Транзакции в .NET: DbTransaction, TransactionScope

↑ 4.1 ADO.NET, Npgsql и Dapper · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Про ACID на уровне кода: где границы транзакции и почему TransactionScope + async опасен.

Объяснение

await using var conn = await dataSource.OpenConnectionAsync(ct);
await using var tx = await conn.BeginTransactionAsync(IsolationLevel.ReadCommitted, ct);
try
{
    await conn.ExecuteAsync("update accounts set balance = balance - @a where id = @from", new { a, from }, tx);
    await conn.ExecuteAsync("update accounts set balance = balance + @a where id = @to",   new { a, to }, tx);
    await tx.CommitAsync(ct);
}
catch
{
    await tx.RollbackAsync(ct);
    throw;
}

TransactionScope

using var scope = new TransactionScope(TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
    TransactionScopeAsyncFlowOption.Enabled);   // обязателен для async
// ... операции нескольких вызовов...
scope.Complete();

Неявно привлекает все соединения в «ambient» транзакцию. Несколько разных соединений/БД → эскалация до распределённой (MSDTC), что в .NET Core на Linux неподдерживаемо и не рекомендуется.

Уровень изоляцииАномалии, которые возможны
Read Uncommittedгрязное чтение (в PG = Read Committed)
Read Committedнеповторяемое чтение, фантомы
Repeatable Readфантомы (в PG — нет, snapshot)
Serializableнет; возможны ошибки сериализации, нужен retry

EF Core

SaveChanges уже выполняется в транзакции. Явная: db.Database.BeginTransactionAsync(); стратегия повторов (EnableRetryOnFailure) требует оборачивать транзакции в CreateExecutionStrategy().ExecuteAsync.

Нюансы и подводные камни

  • Держите транзакцию короткой: не вызывайте внешний HTTP внутри.
  • Без TransactionScopeAsyncFlowOption.Enabled в async-коде получите исключение или потерю транзакции.
  • Между сервисами и БД распределённые транзакции заменяют сага и outbox (см. этап 8).
  • При deadlock/serialization failure — повторяйте транзакцию целиком.
  • Забытый Complete() = откат.

Практика

  1. Реализуйте перевод денег с откатом при ошибке.
  2. Воспроизведите неповторяемое чтение на двух сессиях psql.
  3. Обработайте serialization failure ретраем.

Вопросы с ответами

Связанные темы