TL; DR / Resumen ejecutivo: Con respecto a esta parte de la pregunta:
No veo en qué casos se puede pasar el control dentro CATCHcon una transacción que se puede confirmar cuando XACT_ABORTse establece enON .
He hecho bastantes pruebas sobre esto ahora y no puedo encontrar ningún caso en el que XACT_STATE()regrese 1dentro de un CATCHbloque cuándo @@TRANCOUNT > 0 y la propiedad de sesión de XACT_ABORTes ON. Y, de hecho, según la página actual de MSDN para SET XACT_ABORT :
Cuando SET XACT_ABORT está activado, si una instrucción Transact-SQL genera un error de tiempo de ejecución, la transacción completa se termina y se revierte.
Esa declaración parece estar de acuerdo con su especulación y mis hallazgos.
El artículo sobre MSDN SET XACT_ABORTtiene un ejemplo cuando algunas declaraciones dentro de una transacción se ejecutan con éxito y otras fallan cuando XACT_ABORTse estableceOFF
Es cierto, pero las declaraciones en ese ejemplo no están dentro de un TRYbloque. Esas mismas declaraciones dentro de un TRYbloque serían aún impiden la ejecución de las manifestaciones después de la que ha provocado el error, pero suponiendo que XACT_ABORTes OFF, cuando se pasa el control al CATCHbloque 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, si XACT_ABORTes ONasí, cualquier cambio anterior se revierte automáticamente, y luego se le da la opción de: a) emitir unROLLBACKlo cual es principalmente una aceptación de la situación, ya que la transacción ya se ha revertido menos restablecer @@TRANCOUNTa 0ob) 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_ABORTes 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 la TRY...CATCHconstrucción que fue introducido en SQL Server 2005. Al volver a ver esa documentación y ver el ejemplo ( sin el TRY...CATCH), el uso XACT_ABORT ONprovoca 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 esa SET XACT_ABORTdocumentación).
Creo que es razonable concluir que:
- La introducción de la
TRY...CATCHconstrucción en SQL Server 2005 creó la necesidad de un nuevo estado de transacción (es decir, "no confirmable") y la XACT_STATE()función para obtener esa información.
- verificar
XACT_STATE()un CATCHbloque realmente solo tiene sentido si se cumple lo siguiente:
XACT_ABORTes OFF(de lo contrario XACT_STATE(), siempre debe volver -1y @@TRANCOUNTsería todo lo que necesita)
- Tiene lógica en el
CATCHbloque, o en algún lugar de la cadena si las llamadas están anidadas, eso hace un cambio (una COMMITo incluso cualquier instrucción DML, DDL, etc.) en lugar de hacer una ROLLBACK. (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 en XACT_STATE()lugar de @@TRANCOUNT, y por qué las pruebas muestran que su razonamiento no funciona.
- La introducción de la
TRY...CATCHconstrucción en SQL Server 2005, en su mayor parte, ha obsoleto la XACT_ABORT ONpropiedad de la sesión, ya que proporciona un mayor grado de control sobre la transacción (al menos tiene la opción COMMIT, siempre que XACT_STATE()no regrese -1).
Otra forma de ver esto es, antes de SQL Server 2005 , XACT_ABORT ONproporcionar una forma fácil y confiable de detener el procesamiento cuando se produjo un error, en comparación con la verificación @@ERRORdespués de cada declaración.
- El código de ejemplo de documentación para
XACT_STATE()es erróneo o, en el mejor de los casos, engañoso, ya que muestra la comprobación de XACT_STATE() = 1cuándo XACT_ABORTes ON.
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 CATCHbloque cuando lo XACT_ABORTes ON), 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.
Si hay un error suficientemente grave dentro del bloque TRY, 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?
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 un 1, eso significa que la transacción es "comprometible", que puede elegir entre COMMITo ROLLBACK. 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 hacerlo ROLLBACKporque 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 ONcosas cambia un poco. Puede tener un error regular, después de hacerlo BEGIN TRANpasará el control al bloque CATCH cuando XACT_ABORTsea OFFy XACT_STATE () volverá 1. PERO, si XACT_ABORT es ON, entonces la transacción es "abortada" (es decir, invalidada) por cualquier error y luego XACT_STATE()regresará -1. En este caso, parece inútil verificar XACT_STATE()dentro del CATCHbloque, ya que siempre parece devolver un -1cuándo XACT_ABORTes ON.
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:
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.
La página de MSDN para SET XACT_ABORT , en la sección "Comentarios", dice:
Cuando SET XACT_ABORT está desactivado, en algunos casos solo se revierte la instrucción Transact-SQL que provocó el error y la transacción continúa procesándose.
y:
XACT_ABORT debe estar ACTIVADO para las declaraciones de modificación de datos en una transacción implícita o explícita contra la mayoría de los proveedores de OLE DB, incluido SQL Server.
La página de MSDN para COMENZAR TRANSACCIÓN , en la sección "Comentarios", dice:
La transacción local iniciada por la instrucción BEGIN TRANSACTION se escala a una transacción distribuida si se realizan las siguientes acciones antes de que la declaración se confirme o se revierta:
- Se ejecuta una instrucción INSERT, DELETE o UPDATE que hace referencia a una tabla remota en un servidor vinculado. La instrucción INSERT, UPDATE o DELETE falla si el proveedor OLE DB utilizado para acceder al servidor vinculado no es compatible con la interfaz ITransactionJoin.
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_ABORTconfigurado en ONlugar de no verificar XACT_STATE(), o posiblemente ambos. Y recuerdo haber leído algo que decía que si usa Servidores Vinculados y / o Transacciones Distribuidas, entonces necesitaba usar XACT_ABORT ONy / o XACT_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 ONy pase el control al CATCHbloque con XACT_STATE()informes 1.
Pruebe estos ejemplos para ver el efecto de XACT_ABORTen el valor de XACT_STATE():
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;
ACTUALIZAR
Si bien no es parte de la Pregunta original, en base a estos comentarios en esta Respuesta:
He estado leyendo los artículos de Erland sobre Manejo de errores y transacciones donde dice que XACT_ABORTes OFFpor defecto por razones heredadas y que normalmente deberíamos configurarlo ON.
...
"... si sigue la recomendación y ejecuta SET XACT_ABORT ON, la transacción siempre estará condenada".
Antes de usar en XACT_ABORT ONtodas 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 no ROLLBACKpuede 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 SqlCommandobjeto (es decir, a través de using (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:
- Una transacción "no conmutable" es aquella que, en su mayor parte, ya se ha revertido (los cambios se han deshecho), pero
@@TRANCOUNT aún es> 0.
- Cuando tiene una Transacción "no conmutable", no puede emitir una,
COMMITya 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ícito ROLLBACK, tal vez no en el CATCHbloque inmediato , sino antes de que finalice el lote.
- En una
TRY...CATCHconstrucción, cuando XACT_ABORTes así OFF, los errores que terminarían la transacción automáticamente si hubieran ocurrido fuera de un TRYbloque, como los errores de aborto por lotes, deshacerán el trabajo pero no terminarán la transacción, dejándola como "no transmisible". Emitir a ROLLBACKes más una formalidad necesaria para cerrar la transacción, pero el trabajo ya se ha revertido.
- Cuando
XACT_ABORTes 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 un CATCHbloque, mostrará un -1error de aborto por lotes si hubo una transacción activa en el momento del error.
XACT_STATE()a veces regresa 1incluso cuando no hay una transacción activa. Si @@SPID(entre otros) está en la SELECTlista junto con XACT_STATE(), entonces XACT_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:
- Teniendo en cuenta los puntos 4 y 5, dado que la mayoría (¿o todos los errores?) Harán que una transacción sea "no conmutable", parece completamente inútil verificar
XACT_STATE()en el CATCHbloque cuándo XACT_ABORTes, ONya que el valor devuelto siempre será-1 .
- Comprobación
XACT_STATE()en el CATCHbloque cuando XACT_ABORTse OFFtiene más sentido debido a que el valor de retorno tendrá al menos alguna variación, ya que volverá 1a 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á de ROLLBACKtodos modos simplemente por el hecho de que ocurrió un error.
- Si encuentra una situación que hace orden de emitir una
COMMITen el CATCHbloque, a continuación, comprobar el valor de XACT_STATE(), y asegúrese de SET XACT_ABORT OFF;.
XACT_ABORT ONparece ofrecer poco o ningún beneficio sobre la TRY...CATCHconstrucción.
- No puedo encontrar ningún escenario en el que la verificación
XACT_STATE()proporcione un beneficio significativo sobre la simple verificación @@TRANCOUNT.
- Tampoco puedo encontrar ningún escenario donde
XACT_STATE()regrese 1en un CATCHbloque cuando XACT_ABORTesON . Creo que es un error de documentación.
- Sí, puede revertir una transacción que no comenzó explícitamente. Y en el contexto del uso
XACT_ABORT ON, es un punto discutible ya que un error que ocurre en un TRYbloque revertirá automáticamente los cambios.
- El
TRY...CATCHconstructo tiene el beneficio XACT_ABORT ONde no cancelar automáticamente la Transacción completa y, por lo tanto, permitir que la Transacción (siempre que se XACT_STATE()devuelva 1) se confirme (incluso si este es un caso límite).
Ejemplo de XACT_STATE()regresar -1cuando XACT_ABORTes OFF:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT CONVERT(INT, 'g') AS [ConversionError];
COMMIT TRAN;
END TRY
BEGIN CATCH
DECLARE @State INT;
SET @State = XACT_STATE();
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
@State AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage];
IF (@@TRANCOUNT > 0)
BEGIN
SELECT 'Rollin back...' AS [Transaction];
ROLLBACK;
END;
END CATCH;
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):
- El comportamiento extraño / erróneo comenzó en SQL Server 2012 (hasta ahora probado contra 2012 SP2 y 2014 SP1)
- En las versiones 2005, 2008 y 2008 R2 de SQL Server,
XACT_STATE()no se informaban los valores esperados cuando se usaban en Disparadores o INSERT...EXECescenarios: 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 incorrectamente 1cuando se utiliza en una SELECTcon @@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:
Tenga en cuenta que una instrucción IF, y también un SELECT sin FROM, no inician una transacción.
por ejemplo, si ejecuta SELECT XACT_STATE () si no tiene una transacción previamente existente, devolverá 0.
Esas declaraciones son incorrectas dado el siguiente ejemplo:
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
GO
DECLARE @SPID INT;
SET @SPID = @@SPID;
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
GO
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:
@@ trancount devuelve el número de declaraciones BEGIN TRAN. Por lo tanto, no es un indicador confiable de si hay una transacción activa. XACT_STATE () también devuelve 1 si hay una transacción de confirmación automática activa y, por lo tanto, es un indicador más confiable de si hay una transacción activa.
Sin embargo, no puedo encontrar ninguna razón para no confiar @@TRANCOUNT. La siguiente prueba muestra que @@TRANCOUNTefectivamente regresa 1en una transacción de confirmación automática:
--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
XACT_STATE() AS [XactState];
GO
--- end setup
DECLARE @Test TABLE (TranCount INT, XactState INT);
SELECT * FROM @Test; -- no rows
EXEC #TransactionInfo; -- 0 for both fields
INSERT INTO @Test (TranCount, XactState)
EXEC #TransactionInfo;
SELECT * FROM @Test; -- 1 row; 1 for both fields
También probé en una tabla real con un Trigger y @@TRANCOUNTdentro del Trigger hice un informe preciso 1a pesar de que no se había iniciado ninguna Transacción explícita.
XACT_ABORTaONoOFF.