Transacción y Try-catch en el trabajo de SQL Server

9

Tenemos operaciones DML en cada paso de un trabajo de SQL Server. Para garantizar la actualización / inserción se pondrá en marcha de nuevo en caso de que algo va mal, he envuelto las modificaciones de los datos de cada paso en TRY CATCHy TRANSACTIONbloques:

BEGIN TRY
    BEGIN TRANSACTION

        [[INSERT/update statements]] ...

    IF @@TRANCOUNT > 0
    BEGIN
        COMMIT TRANSACTION
        PRINT 'Successful.'
    END

END TRY

BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() AS ErrorState,
        ERROR_PROCEDURE() AS ErrorProcedure,
        ERROR_LINE() AS ErrorLine,
        ERROR_MESSAGE() AS ErrorMessage

    IF @@TRANCOUNT > 0
    BEGIN
        ROLLBACK TRANSACTION
        PRINT 'Unsuccessful.'
    END
END CATCH

¿Asegura que las manipulaciones de datos se reviertan en caso de error (s)? ¿O deben tenerse en cuenta otras consideraciones?

¿Sería una mejor manera de hacerlo (usando configuraciones, etc.)?

Gracias.

Cielo
fuente

Respuestas:

7

Prefiero recomendar un patrón como el de Manejo de excepciones y transacciones anidadas :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end

Este patrón verifica XACT_STATE()en el bloque catch para evitar transacciones no confirmables :

Transacciones no comprometidas y XACT_STATE
Si un error generado en un bloque TRY hace que se invalide el estado de la transacción actual, la transacción se clasifica como una transacción no confirmable. Un error que normalmente finaliza una transacción fuera de un bloque TRY hace que una transacción entre en un estado no confirmable cuando el error ocurre dentro de un bloque TRY. Una transacción no confirmable solo puede realizar operaciones de lectura o una TRANSACCIÓN ROLLBACK. La transacción no puede ejecutar ninguna instrucción Transact-SQL que genere una operación de escritura o una TRANSACCIÓN DE COMPROMISO. La función XACT_STATE devuelve un valor de -1 si una transacción se ha clasificado como una transacción no confirmable. Cuando finaliza un lote, el Motor de base de datos revierte las transacciones no confirmables activas. Si no se envió un mensaje de error cuando la transacción entró en un estado no confirmable, cuando finalice el lote, se enviará un mensaje de error a la aplicación cliente. Esto indica que se detectó una transacción no confirmada y se revertió.

Su código está buscando @@TRANCOUNTlugares donde no puede ser 0, utiliza una mezcla de mensajes PRINT informativos y conjuntos de resultados SELECT para comunicar con éxito, no maneja errores que son recuperables. Idealmente, las excepciones deberían propagarse al cliente, en este caso al trabajo del Agente (es decir, su captura debería volver a subir).

Remus Rusanu
fuente
¡Gracias por su útil respuesta y su fantástico sitio web! Sin embargo, me pregunto si aún puedo usar este patrón con una simple declaración DML (no un proceso almacenado). ¿También tenemos que guardar la transacción como se muestra a continuación? (No tengo un proceso de tienda para usar): guardar la transacción usp_my_procedure_name;
Sky
2

Lo que tienes me parece bien. Sugeriría hacer algo con la información, por supuesto, después de que haya revertido la transacción, por ejemplo, escríbala en un registro.

datagod
fuente
1
Gracias por su respuesta, ¿podría darme una pista sobre cómo escribirlo en un registro?
Cielo
3
Si desea escribir errores o datos en una tabla de registro, antes de hacer la reversión, copie los datos que desea en una variable de tabla (es importante que use una variable de tabla, una tabla temporal se revertirá) y luego realice la reversión, luego inserte los datos de la variable de tabla en la tabla de registro.
HLGEM