TL; DR / Resumen ejecutivo: Con respecto a esta parte de la pregunta:
No veo en qué casos se puede pasar el control dentro CATCH
con una transacción que se puede confirmar cuando XACT_ABORT
se establece enON
.
He hecho bastantes pruebas sobre esto ahora y no puedo encontrar ningún caso en el que XACT_STATE()
regrese 1
dentro de un CATCH
bloque cuándo @@TRANCOUNT > 0
y la propiedad de sesión de XACT_ABORT
es 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_ABORT
tiene un ejemplo cuando algunas declaraciones dentro de una transacción se ejecutan con éxito y otras fallan cuando XACT_ABORT
se estableceOFF
Es cierto, pero las declaraciones en ese ejemplo no están dentro de un TRY
bloque. Esas mismas declaraciones dentro de un TRY
bloque serían aún impiden la ejecución de las manifestaciones después de la que ha provocado el error, pero suponiendo que XACT_ABORT
es OFF
, cuando se pasa el control al CATCH
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, si XACT_ABORT
es ON
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
a 0
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 la TRY...CATCH
construcció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 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 esa SET XACT_ABORT
documentación).
Creo que es razonable concluir que:
- La introducción de la
TRY...CATCH
construcció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 CATCH
bloque realmente solo tiene sentido si se cumple lo siguiente:
XACT_ABORT
es OFF
(de lo contrario XACT_STATE()
, siempre debe volver -1
y @@TRANCOUNT
sería todo lo que necesita)
- Tiene lógica en el
CATCH
bloque, o en algún lugar de la cadena si las llamadas están anidadas, eso hace un cambio (una COMMIT
o 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...CATCH
construcción en SQL Server 2005, en su mayor parte, ha obsoleto la XACT_ABORT ON
propiedad 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 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.
- 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() = 1
cuándo XACT_ABORT
es 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 CATCH
bloque cuando lo XACT_ABORT
es 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 COMMIT
o 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 ROLLBACK
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 hacerlo BEGIN TRAN
pasará el control al bloque CATCH cuando XACT_ABORT
sea OFF
y 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 CATCH
bloque, ya que siempre parece devolver un -1
cuándo XACT_ABORT
es 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_ABORT
configurado en ON
lugar 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 ON
y / 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 ON
y pase el control al CATCH
bloque con XACT_STATE()
informes 1
.
Pruebe estos ejemplos para ver el efecto de XACT_ABORT
en 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_ABORT
es OFF
por 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 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 no ROLLBACK
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 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,
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ícito ROLLBACK
, tal vez no en el CATCH
bloque inmediato , sino antes de que finalice el lote.
- En una
TRY...CATCH
construcción, cuando XACT_ABORT
es así OFF
, los errores que terminarían la transacción automáticamente si hubieran ocurrido fuera de un TRY
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 a ROLLBACK
es más una formalidad necesaria para cerrar la transacción, pero el trabajo ya se ha revertido.
- Cuando
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 un CATCH
bloque, mostrará un -1
error de aborto por lotes si hubo una transacción activa en el momento del error.
XACT_STATE()
a veces regresa 1
incluso cuando no hay una transacción activa. Si @@SPID
(entre otros) está en la SELECT
lista 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 CATCH
bloque cuándo XACT_ABORT
es, ON
ya que el valor devuelto siempre será-1
.
- Comprobación
XACT_STATE()
en el CATCH
bloque cuando XACT_ABORT
se OFF
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á de ROLLBACK
todos modos simplemente por el hecho de que ocurrió un error.
- Si encuentra una situación que hace orden de emitir una
COMMIT
en el CATCH
bloque, a continuación, comprobar el valor de XACT_STATE()
, y asegúrese de SET XACT_ABORT OFF;
.
XACT_ABORT ON
parece ofrecer poco o ningún beneficio sobre la TRY...CATCH
construcció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 1
en un CATCH
bloque cuando XACT_ABORT
esON
. 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 TRY
bloque revertirá automáticamente los cambios.
- El
TRY...CATCH
constructo tiene el beneficio XACT_ABORT ON
de 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 -1
cuando XACT_ABORT
es 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...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 incorrectamente 1
cuando se utiliza en una SELECT
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:
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 @@TRANCOUNT
efectivamente regresa 1
en 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 @@TRANCOUNT
dentro del Trigger hice un informe preciso 1
a pesar de que no se había iniciado ninguna Transacción explícita.
XACT_ABORT
aON
oOFF
.