SQL Server两张无关联表DELETE操作死锁问题咨询
SQL Server跨表死锁排查思路
首先明确核心前提:不存在完全无关联的跨表死锁,所有看似“无关”的死锁,本质都是没找到两个事务之间的锁持有/等待闭环。
你当前观测到的只是两个事务正在申请的锁,不是死锁的全貌:
- 事务1:执行test1表DELETE操作,正在申请test1表的对象锁,最终被选作死锁牺牲品终止
- 事务2:执行test2表DELETE操作,正在申请test2表对应页的页锁,最终执行完成
按以下优先级排查,90%以上的同类场景都能定位根因:
1. 优先排查锁升级导致的交叉等待
SQL Server默认触发锁升级的阈值是单语句在单个对象上持有超过5000个行/页级锁,触发后会直接申请对应表的对象锁,这也是你看到事务1申请表锁的最常见原因。
这个场景下最容易踩的坑是只看当前申请的锁,不看事务已经持有的锁:
- 事务1的DELETE大概率已经在test1上拿了大量行/页锁,触发锁升级要拿表锁,但这个申请被其他会话持有的不兼容锁挡住了
- 事务2绝不是只持有test2上的锁:它很可能在同一事务的前序步骤里,已经持有了test1上的某个行/页/表级锁,和事务1要申请的test1对象锁互斥;而事务1已经持有的锁,又刚好和事务2要申请的test2页锁互斥,形成循环等待。
2. 排查你没注意到的隐式跨表加锁逻辑
很多时候你写的DELETE是单表操作,但SQL Server执行时会自动跨表加锁:
- 检查两张表是否存在外键关联:外键列如果没有建对应索引,删除主表记录时SQL Server会扫描整个从表加锁,哪怕你的DELETE语句完全没写关联从表的逻辑
- 检查两张表是否绑定了DELETE触发器:触发器内的增删改操作会在同一事务上下文里执行,持有的锁直到事务提交才会释放,很容易形成跨表锁交叉
- 检查事务是否开启了隐式事务:很多场景下会话在执行当前DELETE之前,已经跑过其他跨表增删改语句但没提交,锁一直持有到当前语句执行才触发等待,你如果只抓取当前正在运行的DELETE语句,根本看不到前序的跨表操作。
3. 补全死锁的锁持有链路
死锁成立的必要条件是两个事务各自持有对方需要的锁,你现在拿到的只是等待端的信息,必须从死锁日志里把两个事务已经持有的所有锁、事务启动后执行过的所有语句都拉出来,就能直接拼出等待闭环。
给一个这类跨表死锁最典型的闭环参考:
- 事务1先执行test2表的DELETE操作,持有test2某页的排他页锁
- 事务2先执行test1表的DELETE操作,持有test1若干行/页的排他锁
- 事务1继续执行test1表的批量DELETE,累计拿锁超过阈值触发锁升级,申请test1表的排他对象锁,被事务2持有的test1锁阻塞,进入等待
- 事务2继续执行test2表的DELETE,申请test2对应页的排他页锁,被事务1持有的test2页锁阻塞,循环等待形成,死锁触发
快速验证&修复手段
- 用扩展事件或1222跟踪标志抓取完整死锁日志,重点核对两个事务的已持有锁列表、事务内全量执行语句
- 如果确认是锁升级导致的问题,可以优化DELETE逻辑,分批次删除、单次操作行数控制在5000以内,也可以按需针对表禁用锁升级
- 排查外键列的索引,所有外键列都建上对应索引,避免删主表时锁全表
- 如果是触发器或隐式事务导致的锁持有时间过长,尽量缩短事务长度,把不需要放在事务里的逻辑移到事务外执行,减少锁交叉概率
内容的提问来源于stack exchange,提问作者prakash m n
相关产品推荐
相关产品推荐

