sql server - try - ¿Realmente necesito usar "SET XACT_ABORT ON"?
error_message() sql server (6)
Creo que SET XACT_ABORT ON era un requisito al ejecutar transacciones distribuidas.
De los libros en línea: XACT_ABORT debe establecerse para 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. El único caso donde no se requiere esta opción es si el proveedor admite transacciones anidadas. Para obtener más información, vea Consultas distribuidas y Transacciones distribuidas.
si tienes cuidado y usas TRY-CATCH para todo, y revertir los errores, realmente necesitas usar:
SET XACT_ABORT ON
En otras palabras, ¿hay algún error que TRY-CATCH perderá que SET XACT_ABORT ON manejará?
Cuando XACT_ABORT se establece en OFF en el disparador y llamo a RAISEERROR en el cuerpo del disparador, los cambios no se retrotraen.
Hay una advertencia a ciegas siempre usando SET XACT_ABORT ON
; que me quemó recientemente.
Leí un argumento convincente sobre que sugería que siempre debería usar XACT_ABORT ON
. Cambié el sistema para establecer esa opción durante la conexión. Excepto que conduce a corrupción de datos y mucho dolor .
begin transaction
try
perform insert
catch duplicate key violation and react appropriately
perform more actions
commit transaction
catch
rollback transaction
end
Excepto que sus "más acciones" ya no estarán sucediendo en una transacción. Porque aunque captó la violación de clave duplicada , el servidor ya no está en una transacción:
begin transaction
try
perform insert
catch duplicate key violation and react appropriately
transaction implicitly rolled back
perform more actions
commit transaction -> fails because not in a transaction
catch
rollback transaction -> fails because not i a transaction
end
Desde entonces, me he revertido. Nunca use SET XACT_ABORT ON
.
Editar : La gente parece pensar que el problema proviene de intentar llamar a ROLLBACK TRANSACTION
mientras no está en una transacción. Creen que el problema puede solucionarse al no llamar a ROLLBACK
si una transacción no está en progreso.
Usemos un pseudo-código, con los cambios de nombre para proteger el NDA:
const
SQLNativeErrorPrimaryKeyViolation = 2627; //Primary keys. 2601 is for other unique index
void x(String sql)
{
database.Connection.ExecuteNoRecords(sql);
}
que es una manera pedante de hacer que esta respuesta sea más legible; usamos x
para representar la ejecución de alguna declaración SQL:
void DoStuff()
{
x("BEGIN TRANSACTION");
try
{
try
{
x("INSERT INTO Patrons (AccountNumber, Name, Gender)"+
"VALUES (619, ''Shelby Jackson'', ''W''");
}
catch (ESqlServerException e)
{
//check if the patron already exists (or some other hypothetical situation arises)
if (e.NativeError == SQLNativeErrorPrimaryKeyViolation)
{
//This patron already exists. Set their frob to grob because contoso the blingblong
x("UPDATE Patrons SET Frob=''Grob'' WHERE AccountNumber = 619");
//20110918: Dont forget we also need to bang the gardinker
x("EXECUTE BangTheGardinker @floof=619");
}
else
throw e;
}
//Continue with the stuff
x("EXECUTE Frob(''{498BBB4D-D9F7-4438-B7A6-4AB5D57937C0}'')");
//All done, commit the transaction
x("COMMIT TRANSACTION");
}
catch (Exception e)
{
//Something bad happened, rollback the transaction
//(if SQL Server didn''t kill the transaction without our permission)
x("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION");
throw e;
}
}
XACT_ABORT ON es genial, permite usarlo
Entonces, ese código funciona. Si hay un error que esperamos , lo manejamos y continuamos. Esto se llama manejar el error . Si ocurre alguna excepción desconocida (algo que no esperábamos), rollback
cualquier transacción que pueda estar en progreso.
Ahora veamos si seguimos ciegamente la sugerencia de que XACT_ABORT
siempre debe estar XACT_ABORT
:
DbConnection Connection()
{
if (_connection == null)
{
_connection = new SqlConnection();
//It is generally recommended that you always have xact_abort on.
//If a connection is closed with a transaction still in progress
//it still leaves locks held until that connection is finally recycled
//Also, when querying linked severs in a client-side transaction, the
//operation won''t work until xact_abort is on (SQL Server will throw an saying xactabort is off
_connection.ExecuteNoRecords("SET XACT_ABORT ON");
}
return _connection;
}
void x(String sql)
{
database.Connection.ExecuteNoRecords(sql);
}
¿Ves la corrupción que causará en DoStuff ?
DoStuff fue escrito correctamente para manejar casos de error. Pero la introducción de XACT_ABORT ON
a la conexión ahora causará daños en la base de datos. Para aquellos de ustedes que no ven el error, veamos el código:
void DoStuff()
{
x("BEGIN TRANSACTION");
try
{
try
{
x("INSERT INTO Patrons (AccountNumber, Name, Gender)"+
"VALUES (619, ''Shelby Jackson'', ''W''");
}
catch (ESqlServerException e)
{
//WARNING: WE ARE NO LONGER OPERATING IN A TRANASCTION
//Because XACT_ABORT is on, the transaction that we started has been implicitly rolled back.
//From now on, we are no longer in a transaction. If another error happens
//the changes we make cannot be rolled back
//check if the patron already exists (or some other hypothetical situation arises)
if (e.NativeError == SQLNativeErrorPrimaryKeyViolation)
{
//WARNING: This update happens outside of any transaction!
//This patron already exist. Set their frob to grob because contoso the blingblong
x("UPDATE Patrons SET Frob=''Grob'' WHERE AccountNumber = 619");
//WARNING: This stored procedure happens outside of any transaction!
//20110918: Dont forget we also need to bang the gardinker
x("EXECUTE BangTheGardinker @floof=619");
}
else
throw e;
}
//WARNING: This stored procedure happens outside of any transaction!
//If any error happens from
//Continue with the stuff
x("EXECUTE Frob(''{498BBB4D-D9F7-4438-B7A6-4AB5D57937C0}'')");
//WARNING: This stored procedure happens outside of any transaction. It will throw:
// Msg 3902, Level 16, State 1, Line 1
// The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
//All done, commit the transaction
x("COMMIT TRANSACTION");
}
catch (Exception e)
{
//If there was an error during Frob, we would want to catch it and roll everything back.
//But since SQL Server ended the transaction, we have no way to rollback the changes
//And even if the call to Frob (or Updating the patron''s Grob, or Banging the Gardinder)
//didn''t fail, the call to COMMIT TRANSACTION will throw an error
//Either way, we have detected an error condition that cannot be rolled back in the database
//Something bad happened, rollback the transaction
//(if SQL Server didn''t kill the transaction without our permission)
x("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION");
throw e;
}
}
El código que se escribió correctamente y funciona, se rompe, causa errores y, en el peor, causa daños en la base de datos. Todo porque XACT_ABORT ON
.
Recuerde que hay errores que TRY-CATCH no capturará con o sin XACT_ABORT
.
Sin embargo, SET XACT_ABORT ON
no afecta la captura de errores. Sin embargo, garantiza que cualquier transacción se retrotrae / esté condenada. Cuando está "DESACTIVADO", entonces todavía tiene la opción de confirmar o revertir (sujeto a xact_state). Este es el principal cambio de comportamiento para SQL 2005 para XACT_ABORT
Lo que también hace es quitar bloqueos, etc., si el tiempo de espera del comando del cliente entra en acción y el cliente envía la directiva "abortar". Sin SET XACT_ABORT
, los bloqueos pueden permanecer si la conexión permanece abierta. Mi colega (un MVP) y yo lo probamos a fondo al comienzo del año.
Según tengo entendido, incluso si se usa una captura de prueba y no se utiliza ninguna declaración de reversión en un bloque de catch, cualquier transacción incobrable se retrotraerá cuando XACT_ABORT
esté activado.
XACT_ABORT efectivamente afecta el manejo de errores: abortará todo el lote cuando se encuentre un error, y cualquier código que siga a la línea que produjo el error (incluida la comprobación de errores) NUNCA se ejecutará. Hay dos excepciones a este comportamiento: XACT_ABORT es reemplazado por TRY ... CATCH (el bloque CATCH siempre se ejecutará, y las transacciones NO se revertirán automáticamente, solo se harán no modificables), y XACT_ABORT ignorará RAISERROR.