Cómo deshacer cuando se inician 3 procedimientos almacenados desde un procedimiento almacenado

23

Tengo un procedimiento almacenado que solo ejecuta 3 procedimientos almacenados dentro de ellos. Solo estoy usando 1 parámetro para almacenar si el SP maestro es exitoso.

Si el primer procedimiento almacenado funciona bien en el procedimiento almacenado maestro, pero el segundo procedimiento almacenado falla, entonces, ¿revertirá automáticamente todos los SP en el SP maestro o tengo que hacer algún comando?

Aquí está mi procedimiento:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO
usuario2483342
fuente
Si spNewBilling3emite un error, pero que no quieren volver rollo spNewBilling2o spNewBilling1, a continuación, basta con retirar [begin|rollback|commit] transaction createSavebillinginvoicea partir spSavesomename.
Mike

Respuestas:

56

Dado solo el código que se muestra en la pregunta, y suponiendo que ninguno de los tres subprocesos tenga un manejo explícito de transacciones, entonces sí, se detectará un error en cualquiera de los tres subprocesos y ROLLBACKel CATCHbloque retrocederá todo del trabajo.

PERO aquí hay algunas cosas a tener en cuenta sobre las transacciones (al menos en SQL Server):

  • Solo hay una transacción real (la primera), no importa cuántas veces llameBEGIN TRAN

    • Puede nombrar una transacción (como lo ha hecho aquí) y ese nombre aparecerá en los registros, pero nombrar solo tiene significado para la primera transacción / la más externa (porque, de nuevo, la primera es la transacción).
    • Cada vez que llama BEGIN TRAN, ya sea que se llame o no, el contador de transacciones se incrementa en 1.
    • Puedes ver el nivel actual haciendo SELECT @@TRANCOUNT;
    • Cualquier COMMITcomando emitido cuando @@TRANCOUNTestá a 2 o más no hace más que reducir, uno a la vez, el contador de transacciones.
    • Nunca se compromete nada hasta que COMMITse emite un @@TRANCOUNTa1
    • En caso de que la información anterior no indique claramente: independientemente del nivel de transacción, no existe un anidamiento real de las transacciones.
  • Los puntos de guardado permiten crear un subconjunto de trabajo dentro de la transacción que se puede deshacer.

    • Los puntos guardados se crean / marcan mediante el SAVE TRAN {save_point_name}comando
    • Los puntos de guardado marcan el comienzo del subconjunto de trabajo que se puede deshacer sin deshacer toda la transacción.
    • Los nombres de los puntos de guardado no necesitan ser únicos, pero usar el mismo nombre más de una vez aún crea puntos de guardado distintos.
    • Los puntos guardados se pueden anidar.
    • Los puntos guardados no se pueden comprometer.
    • Los puntos guardados se pueden deshacer a través de ROLLBACK {save_point_name}. (más sobre esto a continuación)
    • Revertir un punto de guardado deshacerá cualquier trabajo que haya sucedido después de la llamada más recienteSAVE TRAN {save_point_name} , incluidos los puntos de guardado creados después de que se creó el que se está deshaciendo (de ahí el "anidamiento").
    • Revertir un punto de guardado no tiene efecto en el recuento / nivel de transacción
    • Cualquier trabajo realizado antes de la inicial SAVE TRANno se puede deshacer, excepto mediante la emisión de una ROLLBACKtransacción completa.
    • Para ser claros: emitir un COMMITcuándo @@TRANCOUNTestá en 2 o más, no tiene ningún efecto en los puntos guardados (porque, de nuevo, los niveles de transacción por encima de 1 no existen fuera de ese contador).
  • No puede confirmar transacciones específicas con nombre. El "nombre" de la transacción, si se proporciona junto con el COMMIT, se ignora y solo existe para facilitar la lectura.

  • Una ROLLBACKemisión sin nombre siempre revertirá TODAS las transacciones.

  • Un ROLLBACKemitido con un nombre debe corresponder a:

    • La primera transacción, suponiendo que se llamara:
      suponiendo que no SAVE TRANse haya llamado a ninguna con el mismo nombre de transacción, esto revertirá TODAS las transacciones.
    • Un "punto de guardado" (descrito anteriormente):
      este comportamiento "deshacerá" todos los cambios realizados desde que se llamó al más reciente SAVE TRAN {save_point_name} .
    • Si la primera transacción fue a) nombrada yb) se han SAVE TRANemitido comandos con su nombre, entonces cada ROLLBACK de ese nombre de transacción deshacerá cada punto de guardado hasta que no quede ninguno de ese nombre. Después de eso, un ROLLBACK emitido con ese nombre revertirá TODAS las transacciones.
    • Por ejemplo, suponga que los siguientes comandos se ejecutaron en el orden mostrado:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4

      Ahora, si emite (cada uno de los siguientes escenarios es independiente el uno del otro):

      • ROLLBACK TRAN Buna vez: Deshacerá "DML Query 4". @@TRANCOUNTsigue siendo 2.
      • ROLLBACK TRAN Bdos veces: Deshacerá "DML Query 4" y luego error, ya que no hay un punto de guardado correspondiente para "B". @@TRANCOUNTsigue siendo 2.
      • ROLLBACK TRAN Auna vez: Deshacerá "DML Query 4" y "DML Query 3". @@TRANCOUNTsigue siendo 2.
      • ROLLBACK TRAN Ados veces: Deshacerá "DML Query 4", "DML Query 3" y "DML Query 2". @@TRANCOUNTsigue siendo 2.
      • ROLLBACK TRAN Atres veces: Deshacerá "DML Query 4", "DML Query 3" y "DML Query 2". Luego, revertirá toda la transacción (todo lo que quedó fue "DML Query 1"). @@TRANCOUNTahora es 0.
      • COMMITuna vez: @@TRANCOUNTbaja a 1.
      • COMMITuna vez y luego ROLLBACK TRAN Buna vez: @@TRANCOUNTbaja a 1. Luego deshacerá "DML Query 4" (demostrando que COMMIT no hizo nada). @@TRANCOUNTsigue siendo 1.
  • Nombres de transacciones y nombres de puntos de guardado:

    • puede tener hasta 32 caracteres
    • se trata como si tuviera una intercalación binaria (no distingue entre mayúsculas y minúsculas como dice la documentación actualmente), independientemente de las intercalaciones de nivel de instancia o de base de datos.
    • Para obtener detalles, consulte la sección Nombres de transacciones de la siguiente publicación: ¿Qué hay en un nombre ?: Dentro del mundo loco de los identificadores T-SQL
  • Un procedimiento almacenado no es, en sí mismo, una transacción implícita. Cada consulta si no se ha iniciado una transacción explícita, es una transacción implícita. Es por eso que las transacciones explícitas en torno a consultas individuales no son necesarias a menos que pueda haber una razón programática para hacerlo ROLLBACK, de lo contrario, cualquier error en la consulta es una reversión automática de esa consulta.

  • Cuando se llama a un procedimiento almacenado, debe salir con el valor de @@TRANCOUNTser el mismo que cuando se llamó. Es decir, no puedes:

    • Inicie un BEGIN TRANen el proceso sin comprometerlo, esperando comprometerse en el proceso de llamada / padre.
    • No puede emitir un ROLLBACKsi se inició una transacción explícita antes de que se llame al proceso, ya que volverá @@TRANCOUNTa 0.

    Si sale de un procedimiento almacenado con un recuento de transacciones que es mayor o menor que cuando se inició, obtendrá un error similar a:

    Mensaje 266, Nivel 16, Estado 2, Procedimiento YourProcName, Línea 0
    El recuento de transacciones después de EJECUTAR indica un número de declaraciones BEGIN y COMMIT que no coinciden. Recuento anterior = X, recuento actual = Y.

  • Las variables de tabla, al igual que las variables normales, no están vinculadas por transacciones.


Con respecto al manejo de transacciones en procesos que se pueden llamar de forma independiente (y por lo tanto necesitan manejo de transacciones) o llamar desde otros procesos (por lo tanto, no se necesita manejo de transacciones): esto se puede lograr de dos maneras diferentes.

La forma en que lo he estado manejando durante varios años ahora que parece funcionar bien es solo BEGIN/ COMMIT/ ROLLBACKen la capa más externa. Las llamadas de subproc simplemente omiten los comandos de transacción. He esbozado a continuación lo que pongo en cada proceso (bueno, cada uno que necesita manejo de transacciones).

  • En la parte superior de cada proceso, DECLARE @InNestedTransaction BIT;
  • En lugar de simple BEGIN TRAN, hacer:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • En lugar de simple COMMIT, hacer:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • En lugar de simple ROLLBACK, hacer:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Este método debería funcionar igual independientemente de si la transacción se inició en SQL Server o si se inició en la capa de la aplicación.

Para ver la plantilla completa de este manejo de Transacciones dentro de la TRY...CATCHconstrucción, vea mi respuesta a la siguiente pregunta de DBA.SE: ¿Estamos obligados a manejar Transacciones en Código C # así como en procedimientos almacenados .


Más allá de los "conceptos básicos", hay algunos matices adicionales de las transacciones a tener en cuenta:

  • Por defecto, las transacciones, la mayoría de las veces, no se revierten / cancelan automáticamente cuando se produce un error. Por lo general, esto no es un problema siempre y cuando tenga un manejo adecuado de los errores y se llame a ROLLBACKsí mismo. Sin embargo, a veces las cosas se complican, como en el caso de errores de aborto por lotes, o cuando se usa OPENQUERY(o servidores vinculados en general) y se produce un error en el sistema remoto. Si bien la mayoría de los errores se pueden atrapar usando TRY...CATCH, hay dos que no se pueden atrapar de esa manera (sin embargo, no puedo recordar cuáles en este momento, investigar). En estos casos, debe usar SET XACT_ABORT ONpara deshacer correctamente la transacción.

    SET XACT_ABORT ON hace que SQL Server revierta inmediatamente cualquier transacción (si hay una activa) y cancela el lote si se produce algún error. Esta configuración existía antes de SQL Server 2005, que introdujo la TRY...CATCHconstrucción. En su mayor parte, TRY...CATCHmaneja la mayoría de las situaciones y, por lo tanto, en su mayoría obsoletos la necesidad de XACT_ABORT ON. Sin embargo, cuando lo use OPENQUERY(y posiblemente otro escenario que no puedo recordar en este momento), aún tendrá que usarlo SET XACT_ABORT ON;.

  • Dentro de un activador, XACT_ABORTse establece implícitamente en ON. Esto provoca que cualquier error dentro del Trigger cancele toda la instrucción DML que activó el Trigger.

  • Siempre debe tener un manejo adecuado de los errores, especialmente al usar Transacciones. La TRY...CATCHconstrucción, introducida en SQL Server 2005, proporciona un medio para manejar casi todas las situaciones, una mejora bienvenida sobre las pruebas para @@ERRORdespués de cada declaración, que no ayudó mucho con los errores de aborto por lotes.

    TRY...CATCHintrodujo un nuevo "estado", sin embargo. Cuando no se utiliza la TRY...CATCHconstrucción, si tiene una transacción activa y se produce un error, se pueden tomar varias rutas:

    • XACT_ABORT OFFy error de anulación de la declaración: la transacción aún está activa y el procesamiento continúa con la siguiente declaración , si corresponde.
    • XACT_ABORT OFFy la mayoría de los errores de cancelación de lotes: la transacción aún está activa y el procesamiento continúa con el siguiente lote , si corresponde.
    • XACT_ABORT OFFy ciertos errores de cancelación de lotes: la transacción se revierte y el procesamiento continúa con el siguiente lote , si corresponde.
    • XACT_ABORT ONy cualquier error: la transacción se revierte y el procesamiento continúa con el siguiente lote , si corresponde.


    SIN EMBARGO, cuando se usa TRY...CATCH, los errores de aborto por lotes no abortan el lote, sino que transfieren el control al CATCHbloque. Cuando XACT_ABORTes OFF, la transacción seguirá siendo activa la gran mayoría de las veces, y usted tendrá que COMMIT, o lo más probable, ROLLBACK. Pero cuando encuentre ciertos errores de aborto por lotes (como con OPENQUERY), o cuando lo XACT_ABORTesté ON, la transacción estará en un nuevo estado, "no conmutable". En este estado no puede COMMIT, ni puede hacer ninguna operación DML. Todo lo que puedes hacer es ROLLBACKy SELECTdeclaraciones. Sin embargo, en este estado "incompatible", la transacción fue revertida cuando se produjo el error, y emitirlo ROLLBACKes solo una formalidad, pero debe hacerse.

    Se puede usar una función, XACT_STATE , para determinar si una Transacción está activa, no se puede compartir o no existe. Se recomienda (por lo menos, algunos) verificar esta función en el CATCHbloque para determinar si el resultado es -1( es decir, no comunicable) en lugar de probar si @@TRANCOUNT > 0. Pero con XACT_ABORT ON, eso debería ser el único estado posible para estar, por lo que parece que las pruebas de @@TRANCOUNT > 0y XACT_STATE() <> 0son equivalentes. Por otro lado, cuando XACT_ABORThay OFFy hay una Transacción activa, entonces es posible tener un estado de cualquiera 1o -1en el CATCHbloque, lo que permite la posibilidad de emitir en COMMITlugar de ROLLBACK(aunque, no puedo pensar en un caso cuando alguien querríaCOMMITsi la transacción es commitable). Puede encontrar más información e investigación sobre el uso XACT_STATE()dentro de un CATCHbloque con XACT_ABORT ONmi respuesta a la siguiente pregunta de DBA.SE: ¿En qué casos se puede confirmar una transacción desde dentro del bloque CATCH cuando XACT_ABORT se establece en ON? . Tenga en cuenta que hay un pequeño error XACT_STATE()que hace que regrese falsamente 1en ciertos escenarios: XACT_STATE () devuelve 1 cuando se usa en SELECT con algunas variables del sistema pero sin la cláusula FROM


Notas sobre el código original:

  • Puede eliminar el nombre dado a la transacción ya que no está ayudando a ninguno.
  • No necesita el BEGINy ENDalrededor de cada EXECllamada
Solomon Rutzky
fuente
2
Es una muy buena, buena respuesta.
McNets
1
Wow, esa es una respuesta integral! ¡Gracias! Por cierto, en la página siguiente se abordan los errores a los que se refiere que no están atrapados por Try ... Catch? (Bajo el título "Los errores no afectados por un TRY ... CATCH Construct"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba
1
@jrdevdba Gracias :-). Y de nada. Con respecto a los errores no atrapados, me refería a estos dos: Compile errors, such as syntax errors, that prevent a batch from runningy Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Pero no ocurren con mucha frecuencia, y cuando encuentre una situación así, corríjala (si es un error en el código) o colóquela en un subproceso ( EXECo sp_executesql) para que TRY...CATCHpueda atraparla.
Solomon Rutzky
2

Sí, si debido a algún código de reversión de error en la instrucción catch de su procedimiento almacenado maestro se ejecutará, revertirá todas las operaciones realizadas por cualquier instrucción directa o mediante cualquiera de sus procedimientos almacenados anidados en ella.

Incluso si no ha aplicado ninguna transacción explícita en sus procedimientos almacenados anidados, estos procedimientos almacenados utilizarán una transacción implícita y se confirmarán al finalizar, PERO ya sea que haya confirmado mediante una transacción explícita o implícita en procedimientos almacenados anidados, el motor de SQL Server lo ignorará y lo ignorará. deshaga todas las acciones de estos procedimientos almacenados anidados si el procedimiento almacenado maestro falla y la transacción se respalda.

Cada vez que la transacción se confirma o se revierte en función de la acción realizada al final de la transacción más externa. Si se confirma la transacción externa, las transacciones anidadas internas también se confirman. Si la transacción externa se revierte, todas las transacciones internas también se revierten, independientemente de si las transacciones internas se confirmaron o no individualmente.

Para referencia http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

aasim.abdullah
fuente