Skip to content

事务

本页介绍 Ape‑Volo‑Admin 中的事务能力与使用方式,包含 AOP 注解式事务与工作单元(Unit of Work)两种模式,并给出适用场景与风险规避建议。

AOP 事务开关

  • 在配置中开启:Aop.Tran.Enabled = true(参见“配置文档 > AOP 配置”)。
  • 开启后,被事务特性标记的方法会被拦截并自动包裹在事务中。

事务 AOP

位置:Ape.Volo.Core/Aop/TransactionAop.cs

csharp
/// <summary>
/// 事务拦截器
/// </summary>
public class TransactionAop : IInterceptor
{
    private readonly IUnitOfWork _unitOfWork;

    public TransactionAop(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    /// <summary>
    /// 实例化IInterceptor唯一方法
    /// </summary>
    /// <param name="invocation">包含被拦截方法的信息</param>
    public void Intercept(IInvocation invocation)
    {
        var method = invocation.MethodInvocationTarget ?? invocation.Method;
        //对当前方法的特性验证
        //如果需要验证
        if (method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(UseTranAttribute)) is
            UseTranAttribute)
        {
            try
            {
                _unitOfWork.BeginTran();

                invocation.Proceed();


                // 异步获取,先执行
                if (IsAsyncMethod(invocation.Method))
                {
                    var result = invocation.ReturnValue;
                    if (result is Task)
                    {
                        Task.WaitAll(result as Task);
                    }
                }

                _unitOfWork.CommitTran();
            }
            catch
            {
                _unitOfWork.RollbackTran();
            }
        }
        else
        {
            invocation.Proceed();
        }
    }

    private static bool IsAsyncMethod(MethodInfo method)
    {
        return
            method.ReturnType == typeof(Task) ||
            method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)
            ;
    }
}

事务使用

在需要事务的方法上添加 [UseTran] 特性:

csharp
[UseTran]
public async Task<OperateResult> CreateAsync(CreateUpdateUserDto createUpdateUserDto)
{
    //略
}

工作单元(UnitOfWork)使用

除注解式事务外,亦可在服务层显式控制事务边界,便于更灵活地编排:

csharp
public class UserService : IUserService
{
        private readonly IUnitOfWork _uow;
        private readonly IUserRepository _userRepo;
        private readonly IRoleRepository _roleRepo;

        public UserService(IUnitOfWork uow, IUserRepository userRepo, IRoleRepository roleRepo)
        {
                _uow = uow;
                _userRepo = userRepo;
                _roleRepo = roleRepo;
        }

        public async Task<OperateResult> CreateUserAsync(CreateUpdateUserDto dto)
        {
                try
                {
                        _uow.BeginTran();

                        var user = new User { UserName = dto.UserName, NickName = dto.NickName };
                        await _userRepo.InsertAsync(user);

                        await _roleRepo.BindUserRolesAsync(user.Id, dto.RoleIds);

                        _uow.CommitTran();
                        return OperateResult.Ok();
                }
                catch (Exception)
                {
                        _uow.RollbackTran();
                        throw;
                }
        }
}

提示:工作单元通常封装了底层 ORM(如 SqlSugar)的 BeginTran/CommitTran/RollbackTran,适合在复杂业务编排中精细控制事务范围。

指定隔离级别(如有支持)

IUnitOfWork 提供重载,可在开始事务时指定隔离级别:

csharp
// 示例:指定为 ReadCommitted(以实际支持为准)
_uow.BeginTran(System.Data.IsolationLevel.ReadCommitted);

常见隔离级别简述:

  • ReadCommitted(默认常见):避免脏读,读一致性较好,性能与隔离平衡。
  • ReadUncommitted:可能脏读,一般不推荐。
  • RepeatableRead:避免不可重复读,可能增加锁竞争。
  • Serializable:最高隔离,代价最大,除小范围关键操作外不推荐。

使用 TransactionScope(可选)

若需要环境事务(Ambient Transaction),可使用 TransactionScope。注意异步需开启流动:

csharp
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
        await _userRepo.InsertAsync(user);
        await _roleRepo.BindUserRolesAsync(user.Id, roleIds);
        scope.Complete();
}

注意:跨数据库/跨资源管理器不建议使用分布式事务,优先采用“本地事务 + 事件(Outbox)+ 最终一致性”。


最佳实践与注意事项

  • 缩小事务范围:只包裹必须一致的写操作;将查询、外部调用(HTTP/消息)移出事务。
  • 缩短持锁时间:避免在事务内做长耗时计算/IO;必要时先计算,后入事务快速提交。
  • 合理的更新顺序:约定表/行的更新顺序,降低死锁概率;热点行尽量拆分或排队更新。
  • 幂等与重试:对可能重试的命令设计幂等(如使用业务唯一键);对死锁/临时错误做指数退避重试。
  • 监控与超时:设置事务超时,监控锁等待、死锁、连接池使用率。
  • 与缓存协同:写成功后优先“删缓存再写库”或“写库成功后删缓存/刷新缓存”,避免读到旧数据。

何时应该使用事务

  • 单个用例内涉及多表/多记录写入,需要原子性:创建用户 + 绑定角色/部门;创建订单 + 写入明细 + 扣减库存。
  • 需要保证业务不变量:如同一账号名唯一、资金转账“借贷必相等”。
  • 需要“要么全部成功,要么全部失败”的业务步骤。

不建议使用的场景:

  • 纯读请求或允许短暂最终一致的查询。
  • 跨服务/跨资源的长链路操作(将远程调用放在事务内会显著放大风险)。
  • 大体量批处理长事务(建议分批/分片 + 小事务提交)。

风险与规避

  • 死锁(Deadlock)
    • 规避:约定一致的更新顺序;尽量小事务;必要时添加重试与退避;为查询补充合适索引。
  • 长事务与锁升级
    • 规避:拆分为小事务;避免在事务内等待外部资源;设置合理超时。
  • 隔离级别导致的性能/可见性问题
    • 规避:优先使用 ReadCommitted;如需更高一致性,限定在小范围关键操作中使用更高隔离。
  • 分布式一致性困难
    • 规避:采用本地事务 + 事件外盒(Outbox) + 事件总线,按需补偿,达成最终一致。

小结

  • 简单场景可用 [UseTran] 注解式事务;复杂编排建议使用 IUnitOfWork 显式控制事务边界。
  • 以最小范围、最短时间持锁为目标;关注死锁、长事务与跨服务调用。
  • 跨边界一致性采用事件驱动与最终一致,避免滥用分布式事务。

版权所有 © 2021-2026 ApeVolo-Team