Skip to content

事务管理 Transaction Management ​

目录 ​


事务基础概念 ​

ACID 特性 ​

事务(Transaction) 是数据库操作的原子工作单元,保证数据一致性。

ACID 特性:

A - Atomicity (原子性)
    事务中的所有操作要么全部成功,要么全部失败
    
C - Consistency (一致性)
    事务执行前后,数据库保持一致性状态
    
I - Isolation (隔离性)
    并发事务互不干扰
    
D - Durability (持久性)
    事务提交后,变更永久保存

EF Core 默认事务行为 ​

csharp
// SaveChanges() 自动使用事务
context.Products.Add(new Product { Name = "Product A", Price = 100 });
context.Products.Add(new Product { Name = "Product B", Price = 200 });

await context.SaveChangesAsync();
// ✅ EF Core 自动开启事务
// ✅ 两个插入在同一个事务中
// ✅ 如果任何一个失败,全部回滚

EF Core 事务 API ​

事务接口层次 ​

csharp
IDbContextTransaction
├─ Commit() / CommitAsync()      // 提交事务
├─ Rollback() / RollbackAsync()  // 回滚事务
├─ Dispose()                     // 释放资源
└─ GetDbTransaction()            // 获取底层 DbTransaction

IsolationLevel 枚举:
├─ ReadUncommitted    // 读未提交(最低隔离)
├─ ReadCommitted      // 读已提交(SQL Server 默认)
├─ RepeatableRead     // 可重复读
├─ Serializable       // 串行化(最高隔离)
└─ Snapshot           // 快照隔离

显式事务管理 ​

基本用法 ​

csharp
// 方式 1: using 语句(推荐)
using var transaction = await context.Database.BeginTransactionAsync();

try
{
    context.Orders.Add(order);
    await context.SaveChangesAsync();
    
    orderItems.ForEach(item => context.OrderItems.Add(item));
    await context.SaveChangesAsync();
    
    // 所有操作成功,提交事务
    await transaction.CommitAsync();
}
catch
{
    // 发生异常,回滚事务
    await transaction.RollbackAsync();
    throw;
}

// 方式 2: try-catch-finally
DbTransaction transaction = null;
try
{
    transaction = await context.Database.BeginTransactionAsync();
    
    // 执行操作...
    
    await transaction.CommitAsync();
}
catch
{
    transaction?.Rollback();
    throw;
}
finally
{
    transaction?.Dispose();
}

指定隔离级别 ​

csharp
// SQL Server 示例
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.ReadCommitted);  // 读已提交

// 其他隔离级别
using var transaction1 = await context.Database.BeginTransactionAsync(
    IsolationLevel.Serializable);  // 串行化(最强一致性)

using var transaction2 = await context.Database.BeginTransactionAsync(
    IsolationLevel.Snapshot);      // 快照隔离(高并发)

隔离级别对比 ​

隔离级别脏读不可重复读幻读性能适用场景
ReadUncommitted✅ 可能✅ 可能✅ 可能⭐⭐⭐⭐⭐统计报表
ReadCommitted❌ 不会✅ 可能✅ 可能⭐⭐⭐⭐默认选择
RepeatableRead❌ 不会❌ 不会✅ 可能⭐⭐⭐数据一致性要求高
Serializable❌ 不会❌ 不会❌ 不会⭐⭐金融交易
Snapshot❌ 不会❌ 不会❌ 不会⭐⭐⭐高并发读写

高级事务场景 ​

场景 1: 跨 DbContext 事务 ​

csharp
// 多个 DbContext 共享同一连接和事务
var connection = context1.Database.GetDbConnection();
await connection.OpenAsync();

using var transaction = connection.BeginTransaction();

try
{
    context1.Database.UseTransaction(transaction);
    context2.Database.UseTransaction(transaction);
    
    // 操作 context1
    context1.Orders.Add(order);
    await context1.SaveChangesAsync();
    
    // 操作 context2
    context2.Inventory.Update(stock);
    await context2.SaveChangesAsync();
    
    await transaction.Commit();
}
catch
{
    await transaction.Rollback();
    throw;
}
finally
{
    await connection.CloseAsync();
}

场景 2: 分布式事务 ​

csharp
// 使用 TransactionScope(需要 MSDTC 服务)
using var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions 
    { 
        IsolationLevel = IsolationLevel.ReadCommitted,
        Timeout = TimeSpan.FromMinutes(5) 
    },
    TransactionScopeAsyncFlowOption.Enabled);  // 支持异步

try
{
    // 操作数据库 1
    await context1.SaveChangesAsync();
    
    // 操作数据库 2(可能在另一台服务器)
    await context2.SaveChangesAsync();
    
    // 调用外部 API
    await externalService.ProcessAsync();
    
    // 所有操作成功
    scope.Complete();  // 相当于 Commit
}
catch
{
    // 自动回滚,无需显式调用
    throw;
}

场景 3: 保存点(Savepoint) ​

csharp
// SQL Server 2022+ / PostgreSQL 支持保存点
using var transaction = await context.Database.BeginTransactionAsync();

try
{
    // 第一步操作
    context.Orders.Add(order);
    await context.SaveChangesAsync();
    
    // 创建保存点
    await transaction.CreateSavepointAsync("AfterOrder");
    
    try
    {
        // 第二步操作(可能失败)
        context.Payments.Add(payment);
        await context.SaveChangesAsync();
        
        await transaction.CommitAsync();
    }
    catch
    {
        // 回滚到保存点,保留订单
        await transaction.RollbackToSavepointAsync("AfterOrder");
        
        // 记录失败日志,但订单仍然有效
        _logger.LogWarning("Payment failed for order {OrderId}", order.Id);
        
        await transaction.CommitAsync();  // 提交订单
    }
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

场景 4: 重试策略 ​

csharp
public class ResilientTransaction
{
    private readonly AppDbContext _context;
    private readonly ILogger _logger;
    private const int MaxRetries = 3;
    
    public async Task ExecuteWithRetryAsync(Func<Task> operation)
    {
        var retryCount = 0;
        
        while (retryCount < MaxRetries)
        {
            using var transaction = await _context.Database.BeginTransactionAsync();
            
            try
            {
                await operation();
                
                await transaction.CommitAsync();
                
                _logger.LogInformation("Transaction succeeded on attempt {Attempt}", retryCount + 1);
                return;  // 成功,退出
            }
            catch (Exception ex) when (IsTransientError(ex))
            {
                retryCount++;
                
                if (retryCount >= MaxRetries)
                {
                    _logger.LogError(ex, "Transaction failed after {MaxRetries} attempts", MaxRetries);
                    throw;
                }
                
                _logger.LogWarning(ex, "Transient error, retrying... Attempt {Attempt}/{MaxRetries}", 
                    retryCount, MaxRetries);
                
                // 指数退避等待
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
            }
            catch
            {
                await transaction.RollbackAsync();
                throw;  // 非临时错误,立即抛出
            }
        }
    }
    
    private bool IsTransientError(Exception ex)
    {
        // 检测临时错误(网络问题、死锁等)
        return ex is SqlException sqlEx && 
               (sqlEx.Number == 1205 ||  // 死锁
                sqlEx.Number == -2 ||    // 超时
                sqlEx.Number == 64);     // 连接中断
    }
}

// 使用
var transaction = new ResilientTransaction(context, logger);
await transaction.ExecuteWithRetryAsync(async () =>
{
    context.Orders.Add(order);
    await context.SaveChangesAsync();
});

场景 5: 批量操作事务 ​

csharp
public async Task BulkInsertAsync(List<Product> products)
{
    using var transaction = await context.Database.BeginTransactionAsync();
    
    try
    {
        // 分批插入,每批 1000 条
        var batchSize = 1000;
        var batches = products.Chunk(batchSize);
        
        foreach (var batch in batches)
        {
            context.Products.AddRange(batch);
            await context.SaveChangesAsync();
            
            // 清理 ChangeTracker,避免内存溢出
            context.ChangeTracker.Clear();
        }
        
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

.NET 8/9/10 新特性 ​

.NET 8: 改进的事务性能 ​

csharp
// .NET 8 优化了事务提交性能
// 对于大量小事务,性能提升 15-25%

using var transaction = await context.Database.BeginTransactionAsync();
context.Orders.Add(order);
await context.SaveChangesAsync();
await transaction.CommitAsync();  // 更快!

.NET 9: 增强的诊断 ​

csharp
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString)
           .EnableDetailedErrors()
           .LogTo(Console.WriteLine, LogLevel.Information);
});

// .NET 9 输出详细事务信息:
// info: Beginning transaction with isolation level 'ReadCommitted'
// info: Transaction committed successfully in 5ms
// warn: Transaction rolled back due to exception: DbUpdateException

.NET 10: 智能事务管理(路线图) ​

预计特性:

  • 自动检测并优化事务范围
  • 基于 AI 的死锁预测
  • 事务性能分析工具
  • 自适应隔离级别选择

最佳实践与陷阱 ​

最佳实践 ​

1. 始终使用 using 语句 ​

csharp
// ✅ 推荐: using 确保资源释放
using var transaction = await context.Database.BeginTransactionAsync();
try
{
    // 操作...
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}
// 自动 Dispose

// ❌ 避免: 手动管理生命周期
var transaction = await context.Database.BeginTransactionAsync();
// 💥 如果忘记 Dispose,连接泄漏!

2. 选择合适的隔离级别 ​

csharp
// ✅ 推荐: 大多数场景使用 ReadCommitted
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.ReadCommitted);

// ✅ 高并发场景使用 Snapshot
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.Snapshot);

// ❌ 避免: 过度使用 Serializable(性能差)
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.Serializable);  // 💥 可能导致严重锁竞争

3. 保持事务简短 ​

csharp
// ✅ 推荐: 快速提交
using var transaction = await context.Database.BeginTransactionAsync();
context.Orders.Add(order);
await context.SaveChangesAsync();
await transaction.CommitAsync();  // 几毫秒内完成

// ❌ 避免: 长事务
using var transaction = await context.Database.BeginTransactionAsync();
context.Orders.Add(order);
await context.SaveChangesAsync();

// 💥 长时间运行的外部调用,持有锁!
await externalApi.CallAsync();  // 可能需要几秒

await transaction.CommitAsync();

4. 处理并发冲突 ​

csharp
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.RepeatableRead);  // 防止不可重复读

try
{
    var product = await context.Products.FindAsync(id);
    
    // 业务逻辑...
    
    product.Price = newPrice;
    await context.SaveChangesAsync();
    
    await transaction.CommitAsync();
}
catch (DbUpdateConcurrencyException)
{
    await transaction.RollbackAsync();
    // 处理并发冲突
}

常见陷阱 ​

陷阱 1: 忘记提交或回滚 ​

csharp
// ❌ 错误: 忘记提交
using var transaction = await context.Database.BeginTransactionAsync();
context.Orders.Add(order);
await context.SaveChangesAsync();
// 💥 忘记 Commit,事务超时后自动回滚!

// ✅ 正确: 明确提交
await transaction.CommitAsync();

陷阱 2: 嵌套事务误解 ​

csharp
// ⚠️ 注意: EF Core 不支持真正的嵌套事务
using var outer = await context.Database.BeginTransactionAsync();

using var inner = await context.Database.BeginTransactionAsync();
// 💥 实际上使用的是同一个事务!

// 正确理解: 内层 BeginTransaction 返回的是外层事务的引用

陷阱 3: 异常后继续使用连接 ​

csharp
// ❌ 错误: 异常后未回滚就继续
using var transaction = await context.Database.BeginTransactionAsync();
try
{
    context.Orders.Add(order);
    await context.SaveChangesAsync();
}
catch
{
    // 💥 忘记回滚!
}

// 下一个操作会失败
context.Products.Add(product);  // 💥 异常!

// ✅ 正确: 异常后立即回滚
catch
{
    await transaction.RollbackAsync();
    throw;
}

陷阱 4: 跨异步上下文丢失事务 ​

csharp
// ❌ 错误: 未启用异步流选项
using (var scope = new TransactionScope())
{
    await context.SaveChangesAsync();  // 💥 事务可能丢失
    
    scope.Complete();
}

// ✅ 正确: 启用 AsyncFlowOption
using (var scope = new TransactionScope(
    TransactionScopeOption.Required,
    TransactionScopeAsyncFlowOption.Enabled))  // ← 关键!
{
    await context.SaveChangesAsync();  // ✅ 事务保持
    
    scope.Complete();
}

总结 ​

核心要点 ​

  1. 默认行为: SaveChanges() 自动使用事务
  2. 显式事务: 跨多个 SaveChanges() 保证原子性
  3. 隔离级别: ReadCommitted 适合大多数场景
  4. 资源管理: 始终使用 using 语句
  5. 异常处理: catch 中必须 Rollback

事务选择指南 ​

场景推荐方案原因
单个 SaveChanges默认事务简单高效
多个 SaveChanges显式事务保证原子性
跨 DbContext共享连接+事务跨上下文一致性
跨数据库TransactionScope分布式事务
高并发Snapshot 隔离减少锁竞争
金融交易Serializable最强一致性
批量操作分批+保存点容错能力强

代码模板 ​

csharp
// 模板 1: 基本显式事务
using var transaction = await context.Database.BeginTransactionAsync();
try
{
    // 操作 1
    await context.SaveChangesAsync();
    
    // 操作 2
    await context.SaveChangesAsync();
    
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

// 模板 2: 指定隔离级别
using var transaction = await context.Database.BeginTransactionAsync(
    IsolationLevel.ReadCommitted);

// 模板 3: 重试策略
var retryCount = 0;
while (retryCount < MaxRetries)
{
    using var transaction = await context.Database.BeginTransactionAsync();
    try
    {
        // 操作...
        await transaction.CommitAsync();
        break;
    }
    catch (Exception ex) when (IsTransientError(ex))
    {
        retryCount++;
        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
    }
}

// 模板 4: TransactionScope
using var scope = new TransactionScope(
    TransactionScopeOption.Required,
    TransactionScopeAsyncFlowOption.Enabled);

// 操作...

scope.Complete();

下一步 ​

基于 MIT 许可发布