.NET Core中TransactionScope与DistributedLock混用是否合理?
问题核心解答
两种方案混用是完全正确的,二者解决的是不同维度的问题,属于互补关系,没有冲突。但你当前的代码存在执行顺序错误,需要调整。
为什么TransactionScope无法替代分布式锁
你认为「事务执行期间其他机器无法访问同一行」是对SQL Server事务机制的误解:
- SQL Server默认事务隔离级别为
READ COMMITTED,仅会在实际执行写操作时对目标行加排他锁,读操作不加锁,且行锁仅在写操作执行到事务提交的窗口期生效,并不是事务一创建就加锁。 - 对于
createDocument的插入场景,目标行在插入前根本不存在,没有行锁可以提前占用,并发插入请求完全可能同时进入事务逻辑,导致重复插入、数据冲突。 - 即使你调高事务隔离级别到可重复读、串行化,也只会增加数据库死锁概率,无法从根本上避免多节点的并发竞态问题。
分布式锁和事务的分工
- 分布式锁:解决多节点并发控制问题,确保同一时间只有一个节点可以操作指定Guid的文档,从根源上避免竞态、脏写、重复插入问题。
- TransactionScope:解决本地多操作的原子性问题,确保两个存储库的保存操作要么全部生效,要么全部回滚,是数据一致性的最后保障。
你当前代码的问题和修正
你现在的代码把事务创建放在了分布式锁获取之前,这个顺序会导致严重的性能问题:
多个并发请求会先创建事务占用数据库连接,再抢分布式锁,抢不到锁的请求会阻塞在锁获取阶段,长事务长时间占用连接会快速打满数据库连接池,同时大幅提升数据库死锁概率。
正确的执行顺序应该是先抢分布式锁,锁拿到之后再开事务执行业务操作,修正后代码如下:
public void Save(Document document) { // 先拿分布式锁,拿不到直接退出/重试 using (var lockHandler = documentLock.CreateLockForGuid(document.Guid)) { // 锁获取成功后再开事务 using var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled); try { repositoryOne.Save(document); repositoryTwo.Save(document); scope.Complete(); } catch (Exception ex) { // 此处补充异常日志记录,不要吞异常 throw; } } }
额外注意事项
- 确认你封装的
CreateLockForGuid方法获取的是写锁,读写锁的读锁无法阻止写操作,不符合你的业务场景。 - 分布式锁要设置合理的超时释放时间,避免节点宕机导致锁永久占用。
- 如果你的Save方法是异步的,要确保TransactionScope的异步流配置生效,同时所有存储库操作都用
await调用。
内容的提问来源于stack exchange,提问作者TroubleSomeKubernetes
相关产品推荐
相关产品推荐

