.NET IsolationLevel.ReadCommitted事务生成唯一托运编号偶发重复问题
重复编号问题根因
该问题和事务隔离级别有一定关联,但核心原因是代码的「读-改-写」操作非原子:
- 并发请求可以在同一时间读取到相同的
NextConsignmentNumber值,后续各自执行+1更新操作,最终两个请求返回相同编号,数据库的数值仅累加1次,就会出现重复。 - 你提到的Serializable隔离级别下依然出现问题符合官方文档描述:主流数据库的Serializable隔离级别默认对普通SELECT语句使用快照读,不会加行锁,只有写操作才会触发锁逻辑,所以还是会出现两个事务读到同个值的情况。
- 多租户架构下只有同租户的并发请求会触发该问题,所以只会在极少数高并发场景下复现。
修复方案
方案1:原子更新(最优选择)
直接用数据库层面的原子更新语句,避免拆分读和写两个操作,SQL逻辑示例如下:
UPDATE TenantSettings SET SettingValue = CAST(SettingValue AS INT) + 1 WHERE TenantId = @TenantId AND SettingName = @SettingName OUTPUT DELETED.SettingValue;
你可以基于ABP框架的SettingManager扩展对应原子更新方法,全程仅一次数据库写操作,完全规避并发冲突,性能远高于其他方案,也不需要调整事务隔离级别。
方案2:读操作加排他锁
如果不想修改Setting的底层操作逻辑,可以在读取NextConsignmentNumber时对行加排他锁,让并发的读操作阻塞,保证同一时间只有一个事务能拿到当前值。以SQL Server为例,读取语句增加WITH (UPDLOCK, HOLDLOCK)提示即可。
方案3:乐观锁控制
在租户配置表增加RowVersion版本号字段,更新时校验版本号是否和读取时一致,不一致就重试直到更新成功,该方案适合并发量不高的场景,不需要加锁,但要额外处理重试逻辑。
验证建议
修复完成后可模拟同租户100并发连续创建运单的场景,验证是否还会生成重复编号,确保修复生效。
内容的提问来源于stack exchange,提问作者Rick Wheeler
相关产品推荐
相关产品推荐

