Существует предостережение, чтобы всегда вслепую использовать SET XACT_ABORT ON
; который сжег меня недавно.
Я прочитал убедительный аргумент в StackOverflow, который подсказывал, что следует всегда использовать XACT_ABORT ON
. Я изменил систему, чтобы установить эту опцию во время подключения. Кроме того, это приводит к повреждению данных и много боль .
begin transaction
try
perform insert
catch duplicate key violation and react appropriately
perform more actions
commit transaction
catch
rollback transaction
end
За исключением того, что ваши "дополнительные действия" больше не будут происходить в транзакции. Поскольку даже если вы обнаружили нарушение дубликата ключа , сервер больше не находится в транзакции:
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
С тех пор я изменился. Никогда использовать SET XACT_ABORT ON
.
Редактировать : Люди, кажется, думают, что проблема заключается в попытке вызвать ROLLBACK TRANSACTION
, когда он не находится в транзакции. Они думают, что проблему можно решить, не вызывая ROLLBACK
, если транзакция не выполняется.
Давайте используем псевдокод с изменениями имен для защиты NDA:
const
SQLNativeErrorPrimaryKeyViolation = 2627; //Primary keys. 2601 is for other unique index
void x(String sql)
{
database.Connection.ExecuteNoRecords(sql);
}
, который является педантичным способом сделать этот ответ более читабельным; мы используем x
для представления выполнения некоторого оператора 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 это круто, давайте использовать его
Итак, этот код работает. Если есть ошибка, которую мы ожидаем , мы обрабатываем ее и продолжаем. Это называется обработкой ошибки . Если произойдет исключение неизвестно (чего мы не ожидали), мы rollback
сделаем любую транзакцию, которая может быть в процессе.
Теперь давайте посмотрим, будем ли мы слепо следовать предложению, что 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);
}
Видите ли вы повреждение, которое вызовет DoStuff ?
DoStuff был правильно написан для обработки ошибок. Но введение XACT_ABORT ON
в соединение теперь приведет к повреждению базы данных. Для тех из вас, кто не видит ошибку, давайте пройдемся по коду:
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;
}
}
Код, который был написан правильно и работает, становится неработоспособным, вызывает ошибки и в худшем случае приводит к повреждению базы данных. Все потому что я включил XACT_ABORT ON
.