事务
本页介绍 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显式控制事务边界。 - 以最小范围、最短时间持锁为目标;关注死锁、长事务与跨服务调用。
- 跨边界一致性采用事件驱动与最终一致,避免滥用分布式事务。

