Лучше ли явно вызывать откат транзакции или позволить исключению вызвать неявный откат?
в приведенном ниже коде если при выполнении инструкций SQL возникает какое-либо исключение, мы должны ожидать неявного отката транзакции, поскольку транзакция не была зафиксирована, она выходит за рамки и удаляется:
using (DbTransaction tran = conn.BeginTransaction())
{
//
// Execute SQL statements here...
//
tran.Commit();
}
это выше приемлемая практика, или нужно поймать исключение и явно сделать вызов tran.Откат (), как показано ниже:
using (DbTransaction tran = conn.BeginTransaction())
{
try
{
//
// Execute SQL statements here...
//
tran.Commit();
}
catch
{
tran.Rollback();
throw;
}
}
3 ответов
бывший. Если вы посмотрите образцы MSND на похожие темы, например TransactionScope
, все они выступают за неявный откат. Есть различные причины для этого, но я просто дам вам очень простой: к тому времени, когда вы поймаете исключение, транзакция может иметь уже откат. Многие ошибки отката отложенной транзакции и затем они возвращают управление к клиенту, где ADO.Net вызывает CLR SqlException после в транзакция уже откатилась на сервер (1205 DEADLOCK-типичный пример такой ошибки), поэтому явный Rollback()
вызов, в лучшем случае, no-op, а в худшем-ошибка. Поставщик DbTransaction
(напр. SqlTransaction
) должен знать, как обращаться с этим делом, например. потому что есть явный чат между сервером и клиентом, уведомляющий о том, что транзакция уже откатилась, и Dispose()
метод делает правильную вещь.
вторая причина заключается в том, что операции может быть вложенным, но семантика отката заключается в том, что один откат откат все транзакции, поэтому вам нужно только позвонить один раз (в отличие от Commit()
который фиксирует только внутреннюю самую транзакцию и должен вызываться в паре для каждого начала). Опять,Dispose()
делает правильно.
обновление
образец MSDN для SqlConnection.BeginTransaction()
на самом деле выступает за вторую форму и делает явное Rollback()
на catch
блок. Я подозреваю, технический писатель просто намеревался показать в одном образце как Rollback()
и Commit()
, обратите внимание, как ему нужно было добавить второй блок try / catch вокруг Rollback
чтобы обойти точно некоторые из проблем, о которых я упоминал изначально.
вы можете пойти в любом случае, первый из них более лаконичен,второй-более откровенный.
предостережение при первом подходе было бы, что вызов RollBack
на избавление сделки зависит от конкретной реализации драйвера. Надеюсь, почти все разъемы .NET делают это. SqlTransaction
тут:
private void Dispose(bool disposing)
{
Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID);
if (disposing && (this._innerConnection != null))
{
this._disposing = true;
this.Rollback();
}
}
в MySQL:
protected override void Dispose(bool disposing)
{
if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open)
Rollback();
base.Dispose(disposing);
}
предостережение со вторым подходом - это небезопасно звонить RollBack
другое try-catch
. это прямо указано в документации.
короче говоря, что лучше: это зависит от водителя, но обычно лучше идти первым, по причинам, упомянутым Ремусом.
дополнительно см. что происходит с незафиксированной транзакцией при закрытии соединения? для того, как удаление соединения обрабатывает фиксации и откаты.
Я склонен соглашаться с" неявным " откатом на основе путей исключений. Но, очевидно, это зависит от того, где вы находитесь в стеке и что вы пытаетесь сделать (т. е. класс DBTranscation ловит исключение и выполняет очистку, или он пассивно не "фиксирует"?).
вот случай, когда неявная обработка имеет смысл (возможно):
static T WithTranaction<T>(this SqlConnection con, Func<T> do) {
using (var txn = con.BeginTransaction()) {
return do();
}
}
но, если API отличается, обработка фиксации также может быть (предоставлено, это :
static T WithTranaction<T>(this SqlConnection con, Func<T> do,
Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null)
{
using (var txn = con.BeginTransaction()) {
try {
T t = do();
success(txn); // does it matter if the callback commits?
return t;
} catch (Exception e) {
failure(txn); // does it matter if the callback rolls-back or commits?
// throw new Exception("Doh!", e); // kills the transaction for certain
// return default(T); // if not throwing, we need to do something (bogus)
}
}
}
Я не могу думать о слишком многих случаях, когда явно откат является правильным подходом, за исключением случаев, когда существует строгая политика управления изменениями. Но опять же, я немного медлителен в понимании.