Published on

March 1, 2009

Manejo robusto de errores en SQL Server

En el artículo anterior, discutimos el uso de transacciones para proteger la integridad y consistencia de la comunicación basada en Service Broker en SQL Server 2005 Express Edition. Presentamos un código de muestra que demostraba cómo iniciar una conversación, enviar un mensaje a un destino y almacenar su contenido en una tabla auxiliar local, todo dentro de una sola transacción. Sin embargo, también mencionamos que dependiendo del tipo de problema encontrado, el resultado podría ser diferente al esperado.

En este artículo, demostraremos un enfoque más robusto para el manejo de errores y lo aplicaremos a nuestro objetivo. Encerraremos tanto la recuperación de nuevos mensajes de la cola como la inserción de su contenido en una tabla dentro de una sola transacción. Esto evitará situaciones en las que se recibe un mensaje correctamente pero no tiene una entrada correspondiente en la tabla.

Para manejar los errores de manera más efectiva, utilizaremos los bloques BEGIN TRY/END TRY y BEGIN CATCH/END CATCH, así como introduciremos mejoras adicionales a nuestro código. Capturaremos información más específica sobre un problema invocando las funciones ERROR_NUMBER() y ERROR_MESSAGE(), que devuelven el número de referencia del error y su descripción breve, respectivamente. Esta información se transmitirá al servicio iniciador a través de un mensaje de Service Broker.

Además, utilizaremos la función XACT_STATE(), que devuelve el estado de la transacción actual. Según el valor devuelto por XACT_STATE(), decidiremos si forzar un rollback de la transacción o confirmarla. También introduciremos un mecanismo rudimentario para rastrear mensajes fallidos.

Aquí hay un ejemplo del código:

ALTER PROCEDURE dbo.cspProcessqRecv WITH EXECUTE AS OWNER AS 
BEGIN 
    DECLARE @convHandle UNIQUEIDENTIFIER; 
    DECLARE @msgTypeName SYSNAME; 
    DECLARE @status TINYINT; 
    DECLARE @srvName NVARCHAR(512); 
    DECLARE @srvConName NVARCHAR(256); 
    DECLARE @msgTypeValidation AS NCHAR(2); 
    DECLARE @msgBody NVARCHAR(400); 
    DECLARE @cmd AS NVARCHAR(50); 
    DECLARE @errNum AS INTEGER; 
    DECLARE @errDesc AS NVARCHAR(400); 

    BEGIN TRY 
        BEGIN TRANSACTION; 

        WAITFOR(RECEIVE TOP(1) @convHandle = conversation_handle, @msgTypeName =  message_type_name, @status = status, @srvName = service_name, @srvConName = service_contract_name, @msgTypeValidation = validation, @msgBody = CAST(message_body AS NVARCHAR(400)) FROM qRecv), TIMEOUT 1000; 

        IF(@@rowcount != 0) 
        BEGIN 
            INSERT INTO tbMsgRecv(convHandle, msgTypeName, [status], srvName, srvConName, msgTypeValidation, msgBody) 
            VALUES(@convHandle, @msgTypeName, @status, @srvName, @srvConName, @msgTypeValidation, @msgbody); 
        END 

        CONVERSATION @convHandle; 

        COMMIT TRANSACTION; 
    END TRY 

    BEGIN CATCH 
        SET @errNum = ERROR_NUMBER(); 
        SET @errDesc = ERROR_MESSAGE(); 

        IF (XACT_STATE()) = -1 
        BEGIN 
            ROLLBACK TRANSACTION; 
            BEGIN TRANSACTION; 

            RECEIVE TOP(1) @msgBody = CAST(message_body AS NVARCHAR(400)) FROM qRecv WHERE conversation_handle = @convHandle; 

            SEND ON CONVERSATION @convHandle MESSAGE TYPE [//databaseJournal.com/SQL2005EX/ServiceBroker/msgNV] (@msgBody) 
        END 

        CONVERSATION @convHandle WITH ERROR = @errNum DESCRIPTION = @errDesc; 

        COMMIT TRANSACTION; 
    END 

    IF (XACT_STATE()) = 1 
    BEGIN 
        INSERT INTO tbMsgRecvErr(convHandle, msgTypeName, srvName, srvConName, msgTypeValidation, msgBody) 
        VALUES(@convHandle, @msgTypeName, @srvName, @srvConName, @msgTypeValidation, @msgBody); 
    END 

    CONVERSATION @convHandle WITH ERROR = @errNum DESCRIPTION = @errDesc; 

    COMMIT TRANSACTION; 
END 

Bajo circunstancias favorables, el código encerrado dentro del bloque BEGIN TRY/END TRY extraerá un mensaje de la cola, ingresará su contenido en una tabla y finalizará la conversación. Si ocurre un error que hace que la transacción no se pueda confirmar, se invocará el rollback, colocando el mensaje nuevamente en la cola. Lo procesaremos utilizando otra instrucción RECEIVE y lo enviaremos de vuelta al servicio iniciador junto con un mensaje de error que termina la conversación actual.

Si la transacción aún se puede confirmar, lo haremos después de insertar el mensaje procesado en otra tabla auxiliar, finalizar la conversación y enviar un mensaje de error al servicio iniciador.

Este ejemplo se puede refinar y mejorar aún más para manejar diferentes tipos de mensajes y condiciones de error. Es importante asegurarse de que el iniciador y el destino puedan reaccionar adecuadamente a cada tipo de mensaje devuelto. Alternativamente, puede confiar en el mecanismo incorporado de Service Broker conocido como Detección de mensajes envenenados para manejar condiciones de error.

En el próximo artículo de nuestra serie, exploraremos la Detección de mensajes envenenados con más detalle.

Click to rate this post!
[Total: 0 Average: 0]

Let's work together

Send us a message or book free introductory meeting with us using button below.