He estado leyendo MSDN sobre TRY...CATCH
y XACT_STATE
.
Tiene el siguiente ejemplo que se usa XACT_STATE
en el CATCH
bloque de una TRY…CATCH
construcción para determinar si se debe confirmar o revertir una transacción:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Test XACT_STATE for 0, 1, or -1.
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should
-- be rolled back.
-- XACT_STATE = 0 means there is no transaction and
-- a commit or rollback operation would generate an error.
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT 'The transaction is in an uncommittable state.' +
' Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is active and valid.
IF (XACT_STATE()) = 1
BEGIN
PRINT 'The transaction is committable.' +
' Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
GO
Lo que no entiendo es, ¿por qué debería importarme y verificar qué XACT_STATE
devoluciones?
Tenga en cuenta que el indicador XACT_ABORT
está configurado ON
en el ejemplo.
Si hay un error suficientemente grave dentro del TRY
bloque, el control pasará a CATCH
. Entonces, si estoy dentro de CATCH
, sé que la transacción ha tenido un problema y realmente lo único sensato en este caso es revertirla, ¿no?
Pero, este ejemplo de MSDN implica que puede haber casos en los que se pasa el control CATCH
y aún tiene sentido confirmar la transacción. ¿Podría alguien proporcionar algún ejemplo práctico cuando puede suceder, cuando tiene sentido?
No veo en qué casos se puede pasar el control al interior CATCH
con una transacción que se puede confirmar cuando XACT_ABORT
se establece enON
.
El artículo de MSDN sobre SET XACT_ABORT
tiene un ejemplo cuando algunas declaraciones dentro de una transacción se ejecutan con éxito y otras fallan cuando XACT_ABORT
se establece en OFF
, lo entiendo. Pero conSET XACT_ABORT ON
¿cómo puede suceder que XACT_STATE()
devuelva 1 dentro del CATCH
bloque?
Inicialmente, habría escrito este código así:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END CATCH;
GO
Teniendo en cuenta una respuesta de Max Vernon, escribiría el código de esta manera. Mostró que tiene sentido verificar si hay una transacción activa antes de intentarlo ROLLBACK
. Aún así, con SET XACT_ABORT ON
el CATCH
bloque puede haber una transacción condenada o ninguna transacción en absoluto. Entonces, en cualquier caso, no hay nada que hacer COMMIT
. ¿Me equivoco?
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
IF (XACT_STATE()) <> 0
BEGIN
-- There is still an active transaction that should be rolled back
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
fuente
XACT_ABORT
aON
oOFF
.TL; DR / Resumen ejecutivo: Con respecto a esta parte de la pregunta:
He hecho bastantes pruebas sobre esto ahora y no puedo encontrar ningún caso en el que
XACT_STATE()
regrese1
dentro de unCATCH
bloque cuándo@@TRANCOUNT > 0
y la propiedad de sesión deXACT_ABORT
esON
. Y, de hecho, según la página actual de MSDN para SET XACT_ABORT :Esa declaración parece estar de acuerdo con su especulación y mis hallazgos.
Es cierto, pero las declaraciones en ese ejemplo no están dentro de un
TRY
bloque. Esas mismas declaraciones dentro de unTRY
bloque serían aún impiden la ejecución de las manifestaciones después de la que ha provocado el error, pero suponiendo queXACT_ABORT
esOFF
, cuando se pasa el control alCATCH
bloque de la transacción aún está físicamente válida en el que todos los cambios anteriores sucedió sin error y pueden comprometerse, si ese es el deseo, o pueden revertirse. Por otro lado, siXACT_ABORT
esON
así, cualquier cambio anterior se revierte automáticamente, y luego se le da la opción de: a) emitir unROLLBACK
lo cual es principalmente una aceptación de la situación, ya que la transacción ya se ha revertido menos restablecer@@TRANCOUNT
a0
ob) obtener un error. No hay mucha elección, ¿verdad?Un detalle posiblemente importante para este rompecabezas que no es evidente en esa documentación
SET XACT_ABORT
es que esta propiedad de sesión, e incluso ese código de ejemplo, ha existido desde SQL Server 2000 (la documentación es casi idéntica entre las versiones), anterior a laTRY...CATCH
construcción que fue introducido en SQL Server 2005. Al volver a ver esa documentación y ver el ejemplo ( sin elTRY...CATCH
), el usoXACT_ABORT ON
provoca una reversión inmediata de la transacción: no hay estado de transacción de "no confirmable" (tenga en cuenta que no hay mención en todo un estado de transacción "no confirmable" en esaSET XACT_ABORT
documentación).Creo que es razonable concluir que:
TRY...CATCH
construcción en SQL Server 2005 creó la necesidad de un nuevo estado de transacción (es decir, "no confirmable") y laXACT_STATE()
función para obtener esa información.XACT_STATE()
unCATCH
bloque realmente solo tiene sentido si se cumple lo siguiente:XACT_ABORT
esOFF
(de lo contrarioXACT_STATE()
, siempre debe volver-1
y@@TRANCOUNT
sería todo lo que necesita)CATCH
bloque, o en algún lugar de la cadena si las llamadas están anidadas, eso hace un cambio (unaCOMMIT
o incluso cualquier instrucción DML, DDL, etc.) en lugar de hacer unaROLLBACK
. (este es un caso de uso muy atípico) ** consulte la nota en la parte inferior, en la sección ACTUALIZACIÓN 3, con respecto a una recomendación no oficial de Microsoft de verificar siempre enXACT_STATE()
lugar de@@TRANCOUNT
, y por qué las pruebas muestran que su razonamiento no funciona.TRY...CATCH
construcción en SQL Server 2005, en su mayor parte, ha obsoleto laXACT_ABORT ON
propiedad de la sesión, ya que proporciona un mayor grado de control sobre la transacción (al menos tiene la opciónCOMMIT
, siempre queXACT_STATE()
no regrese-1
).Otra forma de ver esto es, antes de SQL Server 2005 ,
XACT_ABORT ON
proporcionar una forma fácil y confiable de detener el procesamiento cuando se produjo un error, en comparación con la verificación@@ERROR
después de cada declaración.XACT_STATE()
es erróneo o, en el mejor de los casos, engañoso, ya que muestra la comprobación deXACT_STATE() = 1
cuándoXACT_ABORT
esON
.La parte larga ;-)
Sí, ese código de ejemplo en MSDN es un poco confuso (vea también: @@ TRANCOUNT (Rollback) vs. XACT_STATE ) ;-). Y, siento que es engañoso porque muestra algo que no tiene sentido (por la razón por la que está preguntando: ¿puede incluso tener una transacción "comprometible" en el
CATCH
bloque cuando loXACT_ABORT
esON
), o incluso si es posible? todavía se enfoca en una posibilidad técnica que pocos querrán o necesitarán, e ignora la razón por la cual es más probable que la necesite.Creo que ayudaría si nos aseguramos de estar en la misma página con respecto a lo que se entiende por ciertas palabras y conceptos:
"error suficientemente grave": para ser claros, PRUEBE ... CATCH atrapará la mayoría de los errores. La lista de lo que no se detectará aparece en esa página de MSDN vinculada, en la sección "Errores no afectados por una construcción TRY ... CATCH".
"si estoy dentro de CATCH, sé que la transacción ha tenido un problema" ( se agrega em phase ): si por "transacción" se refiere a la unidad lógica de trabajo determinada por usted al agrupar las declaraciones en una transacción explícita, entonces más probable es que sí. Creo que la mayoría de nosotros, la gente de DB, tenderíamos a estar de acuerdo en que deshacernos es "la única cosa sensata que hacer", ya que probablemente tengamos una visión similar de cómo y por qué usamos transacciones explícitas y concebimos qué pasos deberían formar una unidad atómica. de trabajo.
Pero, si te refieres a las unidades de trabajo reales que se están agrupando en la transacción explícita, entonces no, no sabes que la transacción en sí ha tenido un problema. Solo sabe que una instrucción que se ejecuta dentro de la transacción definida explícitamente ha provocado un error. Pero podría no ser una declaración DML o DDL. E incluso si se tratara de una declaración DML, la transacción en sí podría ser comprometible.
Teniendo en cuenta los dos puntos mencionados anteriormente, probablemente deberíamos hacer una distinción entre las transacciones que "no puede" comprometer y las que "no desea" comprometer.
Cuando
XACT_STATE()
devuelve un1
, eso significa que la transacción es "comprometible", que puede elegir entreCOMMIT
oROLLBACK
. Es posible que no desee comprometerse, pero si por alguna razón difícil de encontrar con un ejemplo, por alguna razón, quisiera, al menos podría hacerlo porque algunas partes de la Transacción se completaron con éxito.Pero cuando
XACT_STATE()
devuelve un-1
, entonces realmente necesita hacerloROLLBACK
porque una parte de la Transacción entró en mal estado. Ahora, estoy de acuerdo en que si el control se ha pasado al bloque CATCH, entonces tiene suficiente sentido simplemente verificarlo@@TRANCOUNT
, porque incluso si pudiera confirmar la Transacción, ¿por qué querría hacerlo?Pero si observa en la parte superior del ejemplo, la configuración de las
XACT_ABORT ON
cosas cambia un poco. Puede tener un error regular, después de hacerloBEGIN TRAN
pasará el control al bloque CATCH cuandoXACT_ABORT
seaOFF
y XACT_STATE () volverá1
. PERO, si XACT_ABORT esON
, entonces la transacción es "abortada" (es decir, invalidada) por cualquier error y luegoXACT_STATE()
regresará-1
. En este caso, parece inútil verificarXACT_STATE()
dentro delCATCH
bloque, ya que siempre parece devolver un-1
cuándoXACT_ABORT
esON
.Entonces, ¿
XACT_STATE()
para qué sirve ? Algunas pistas son:La página de MSDN para
TRY...CATCH
, en la sección "Transacciones no comprometidas y XACT_STATE", dice:La página de MSDN para SET XACT_ABORT , en la sección "Comentarios", dice:
y:
La página de MSDN para COMENZAR TRANSACCIÓN , en la sección "Comentarios", dice:
El uso más aplicable parece estar dentro del contexto de las declaraciones DML del servidor vinculado. Y creo que me encontré con esto hace años. No recuerdo todos los detalles, pero tenía algo que ver con que el servidor remoto no estaba disponible, y por alguna razón, ese error no quedó atrapado dentro del bloque TRY y nunca se envió a CATCH, y así fue. un COMPROMISO cuando no debería haberlo hecho. Por supuesto, eso podría haber sido un problema de no haberse
XACT_ABORT
configurado enON
lugar de no verificarXACT_STATE()
, o posiblemente ambos. Y recuerdo haber leído algo que decía que si usa Servidores Vinculados y / o Transacciones Distribuidas, entonces necesitaba usarXACT_ABORT ON
y / oXACT_STATE()
, pero parece que no puedo encontrar ese documento ahora. Si lo encuentro, actualizaré esto con el enlace.Aún así, he intentado varias cosas y no puedo encontrar un escenario que tenga
XACT_ABORT ON
y pase el control alCATCH
bloque conXACT_STATE()
informes1
.Pruebe estos ejemplos para ver el efecto de
XACT_ABORT
en el valor deXACT_STATE()
:ACTUALIZAR
Si bien no es parte de la Pregunta original, en base a estos comentarios en esta Respuesta:
Antes de usar en
XACT_ABORT ON
todas partes, me preguntaría: ¿qué se gana exactamente aquí? No he encontrado que sea necesario hacerlo y, en general, recomiendo que lo use solo cuando sea necesario. Si quiere o noROLLBACK
puede manejarse con la suficiente facilidad usando la plantilla que se muestra en la respuesta de @ Remus , o la que he estado usando durante años que es esencialmente lo mismo pero sin el Punto de guardado, como se muestra en esta respuesta (que maneja llamadas anidadas):¿Estamos obligados a manejar la transacción en el código C #, así como en el procedimiento almacenado?
ACTUALIZACIÓN 2
Hice un poco más de pruebas, esta vez al crear una pequeña aplicación de consola .NET, crear una transacción en la capa de la aplicación, antes de ejecutar cualquier
SqlCommand
objeto (es decir, a través deusing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
), así como usar un error de interrupción por lotes en lugar de solo una declaración -aborting error, y descubrió que:@@TRANCOUNT
aún es> 0.COMMIT
ya que generará un error que dice que la Transacción es "no confirmable". Tampoco puede ignorarlo / no hacer nada, ya que se generará un error cuando el lote termine indicando que el lote se completó con una transacción persistente y no confirmable y se revertirá (por lo tanto, um, si se revierte automáticamente de todos modos, ¿Por qué molestarse en lanzar el error?). Por lo tanto, debe emitir un mensaje explícitoROLLBACK
, tal vez no en elCATCH
bloque inmediato , sino antes de que finalice el lote.TRY...CATCH
construcción, cuandoXACT_ABORT
es asíOFF
, los errores que terminarían la transacción automáticamente si hubieran ocurrido fuera de unTRY
bloque, como los errores de aborto por lotes, deshacerán el trabajo pero no terminarán la transacción, dejándola como "no transmisible". Emitir aROLLBACK
es más una formalidad necesaria para cerrar la transacción, pero el trabajo ya se ha revertido.XACT_ABORT
es asíON
, la mayoría de los errores actúan como aborto por lotes y, por lo tanto, se comportan como se describe en el punto de la viñeta directamente arriba (# 3).XACT_STATE()
, al menos en unCATCH
bloque, mostrará un-1
error de aborto por lotes si hubo una transacción activa en el momento del error.XACT_STATE()
a veces regresa1
incluso cuando no hay una transacción activa. Si@@SPID
(entre otros) está en laSELECT
lista junto conXACT_STATE()
, entoncesXACT_STATE()
devolverá 1 cuando no haya una Transacción activa. Este comportamiento comenzó en SQL Server 2012 y existe en 2014, pero no lo he probado en 2016.Con los puntos anteriores en mente:
XACT_STATE()
en elCATCH
bloque cuándoXACT_ABORT
es,ON
ya que el valor devuelto siempre será-1
.XACT_STATE()
en elCATCH
bloque cuandoXACT_ABORT
seOFF
tiene más sentido debido a que el valor de retorno tendrá al menos alguna variación, ya que volverá1
a errores de los estados-abortar. Sin embargo, si codifica como la mayoría de nosotros, entonces esta distinción no tiene sentido ya que llamará deROLLBACK
todos modos simplemente por el hecho de que ocurrió un error.COMMIT
en elCATCH
bloque, a continuación, comprobar el valor deXACT_STATE()
, y asegúrese deSET XACT_ABORT OFF;
.XACT_ABORT ON
parece ofrecer poco o ningún beneficio sobre laTRY...CATCH
construcción.XACT_STATE()
proporcione un beneficio significativo sobre la simple verificación@@TRANCOUNT
.XACT_STATE()
regrese1
en unCATCH
bloque cuandoXACT_ABORT
esON
. Creo que es un error de documentación.XACT_ABORT ON
, es un punto discutible ya que un error que ocurre en unTRY
bloque revertirá automáticamente los cambios.TRY...CATCH
constructo tiene el beneficioXACT_ABORT ON
de no cancelar automáticamente la Transacción completa y, por lo tanto, permitir que la Transacción (siempre que seXACT_STATE()
devuelva1
) se confirme (incluso si este es un caso límite).Ejemplo de
XACT_STATE()
regresar-1
cuandoXACT_ABORT
esOFF
:ACTUALIZACIÓN 3
Relacionado con el artículo # 6 en la sección ACTUALIZACIÓN 2 (es decir, posible valor incorrecto devuelto por
XACT_STATE()
cuando no hay una Transacción activa):XACT_STATE()
no se informaban los valores esperados cuando se usaban en Disparadores oINSERT...EXEC
escenarios: xact_state () no se puede usar de manera confiable para determinar si una transacción está condenada . Sin embargo, en estos 3 versiones (sólo probado en 2008 R2),XACT_STATE()
no no informar incorrectamente1
cuando se utiliza en unaSELECT
con@@SPID
.Hay un error de conexión presentado contra el comportamiento mencionado aquí, pero está cerrado como "Por diseño": XACT_STATE () puede devolver un estado de transacción incorrecto en SQL 2012 . Sin embargo, la prueba se realizó al seleccionar de un DMV y se concluyó que hacerlo naturalmente tendría una transacción generada por el sistema, al menos para algunos DMV. También se indicó en la respuesta final de MS que:
Esas declaraciones son incorrectas dado el siguiente ejemplo:
Por lo tanto, el nuevo error de conexión:
XACT_STATE () devuelve 1 cuando se usa en SELECT con algunas variables del sistema pero sin la cláusula FROM
TENGA EN CUENTA que en el "XACT_STATE () puede devolver un estado de transacción incorrecto en SQL 2012" Conecte el elemento vinculado directamente arriba, Microsoft (bueno, un representante de) declara:
Sin embargo, no puedo encontrar ninguna razón para no confiar
@@TRANCOUNT
. La siguiente prueba muestra que@@TRANCOUNT
efectivamente regresa1
en una transacción de confirmación automática:También probé en una tabla real con un Trigger y
@@TRANCOUNT
dentro del Trigger hice un informe preciso1
a pesar de que no se había iniciado ninguna Transacción explícita.fuente
La programación defensiva requiere que escriba código que maneje tantos estados conocidos como sea posible, reduciendo así la posibilidad de errores.
Comprobar XACT_STATE () para determinar si se puede ejecutar una reversión es simplemente una buena práctica. Intentar ciegamente una reversión significa que, sin darse cuenta, puede causar un error dentro de su TRY ... CATCH.
Una forma en que una reversión podría fallar dentro de TRY ... CATCH sería si no iniciara una transacción explícitamente. Copiar y pegar bloques de código podría causar esto fácilmente.
fuente
ROLLBACK
no funcionara dentroCATCH
y usted dio un buen ejemplo. Supongo que también puede volverse desordenado rápidamente siTRY ... CATCH ... ROLLBACK
se involucran transacciones anidadas y procedimientos almacenados anidados con los suyos .IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
¿cómo podemos terminar dentro delCATCH
bloque con una transacción comprometible? No me atrevería a cometer algo de basura (posible) desde adentroCATCH
. Mi razonamiento es: si estamos dentro deCATCH
algo, algo salió mal, no puedo confiar en el estado de la base de datos, así que será mejorROLLBACK
lo que tengamos.