SQL Server: ¿las transacciones retroceden por error?

192

Tenemos una aplicación cliente que ejecuta algunos SQL en un SQL Server 2005 como el siguiente:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

Se envía mediante un comando de cadena larga.

Si falla una de las inserciones, o falla alguna parte del comando, ¿SQL Server revierte la transacción? Si no se revierte, ¿tengo que enviar un segundo comando para revertirlo?

Puedo dar detalles sobre la API y el idioma que estoy usando, pero creo que SQL Server debería responder igual para cualquier idioma.

jonathanpeppers
fuente

Respuestas:

204

Puede colocar set xact_abort onantes de su transacción para asegurarse de que sql retrocede automáticamente en caso de error.

Greg B
fuente
1
¿Funcionará esto en MS SQL 2K y superior? Esta parece la solución más simple.
jonathanpeppers
1
Aparece en los documentos de 2000, 2005 y 2008, así que supongo que sí. Lo estamos usando en 2008.
8
¿Necesito apagarlo o es por sesión?
Marc
55
@Marc el alcance de xact_abortestá en el nivel de conexión.
Keith
2
@AlexMcMillan La instrucción DROP PROCEDURE modifica la estructura de la base de datos, a diferencia de INSERT, que solo funciona con los datos. Por lo tanto, no se puede envolver en una transacción. Estoy simplificando demasiado, pero básicamente así es como es.
eksortso
195

Tiene razón en que toda la transacción se revertirá. Debe emitir el comando para revertirlo.

Puede envolver esto en un TRY CATCHbloque de la siguiente manera

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- you can Raise ERROR with RAISEERROR() Statement including the details of the exception
    RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1)
END CATCH
Raj Más
fuente
2
Me gusta más la solución de DyingCactus, la suya es 1 línea de código para cambiar. Si es tuyo, si por alguna razón es mejor (o más confiable), házmelo saber.
jonathanpeppers
13
El intento de captura le permite capturar (y posiblemente corregir) el error y generar un mensaje de error personalizado si es necesario.
Raj More
10
"Capturar y registrar" con más frecuencia que "capturar y corregir", creo.
Quillbreaker
24
La sintaxis de RAISERROR es incorrecta al menos en SQL Server 2008R2 y versiones posteriores. Consulte msdn.microsoft.com/en-us/library/ms178592.aspx para obtener la sintaxis correcta.
Eric J.
2
@BornToCode Para asegurarse de que la transacción exista. Digamos que ha revertido su transacción bajo la condición dada (en el try), pero el código falla después. No hay más transacciones, pero aún estás entrando en el catch.
Gabriel GM
42

Aquí el código con el mensaje de error que funciona con MSSQL Server 2016:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
        DECLARE @ErrorState INT = ERROR_STATE()

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
samwise
fuente
1
Tuve que usar DECLARE @Var TYPE; SET @Var = ERROR;para generar errores en el servidor SQL 2005. De lo contrario, el código anterior para generar errores también funciona para los DB más antiguos. Intentar asignar un valor predeterminado a una variable local es lo que estaba causando el problema.
jtlindsey
Puedes usar un THROW simple; en lugar de declaraciones RAISERROR y ERROR_ *.
rodzmkii
21

Del artículo de MDSN, Control de transacciones (motor de base de datos) .

Si se produce un error de declaración en tiempo de ejecución (como una violación de restricción) en un lote, el comportamiento predeterminado en Motor de base de datos es revertir solo la declaración que generó el error. Puede cambiar este comportamiento utilizando la instrucción SET XACT_ABORT. Después de ejecutar SET XACT_ABORT ON, cualquier error en la declaración de tiempo de ejecución provoca una reversión automática de la transacción actual. SET XACT_ABORT no afecta a los errores de compilación, como los errores de sintaxis. Para obtener más información, vea SET XACT_ABORT (Transact-SQL).

En su caso, revertirá la transacción completa cuando falle cualquiera de las inserciones.

Vitalia
fuente
3
¿Qué necesitamos para manejar los errores de sintaxis? o compilar errores? Si cualquiera de ellos ocurre la transacción entera debe ser revertido
MonsterMMORPG
La captura de errores de compilación / sintaxis es para lo que sirven los proyectos SSDT. :-)
Joe the Coder
10

Si falla una de las inserciones, o falla alguna parte del comando, ¿el servidor SQL revierte la transacción?

No, no lo hace.

Si no se revierte, ¿tengo que enviar un segundo comando para revertirlo?

Claro, deberías emitir en ROLLBACKlugar deCOMMIT .

Si desea decidir si compromete o revierte la transacción, debe eliminar la COMMIToración de la declaración, verificar los resultados de las inserciones y luego emitir COMMITo ROLLBACKdependiendo de los resultados de la verificación.

Quassnoi
fuente
Entonces, si recibo un error, diga "Conflicto de clave principal" ¿Necesito enviar una segunda llamada para revertir? Supongo que eso tiene sentido. ¿Qué sucede si hay un error relacionado con la red, como que la conexión se corta durante una instrucción SQL de ejecución muy larga?
jonathanpeppers
2
Cuando se agota el tiempo de espera de una conexión, el protocolo de red subyacente (p. Ej. Named PipesO TCP) interrumpe la conexión. Cuando se interrumpe una conexión, SQL Serverdetiene todos los comandos que se ejecutan actualmente y revierte la transacción.
Quassnoi
1
Parece que la solución de DyingCactus soluciona mi problema, gracias por la ayuda.
jonathanpeppers
Si necesita abortar ante cualquier error, entonces sí, esta es la mejor opción.
Quassnoi