SELECT操作是否可能触发死锁?SQL Server 2008死锁问题咨询
我来帮你理清这个问题——很多人都会误以为死锁只有写操作才会触发,但SELECT确实能引发死锁,结合你的SQL Server 2008场景,咱们一步步拆解原因和排查方法:
为什么SELECT会搞出死锁?
先纠正一个常见误区:SELECT在SQL Server中也会获取锁,绝非只有写操作才会触发死锁。具体到你的情况,核心原因和这几点有关:
- 默认的
READ COMMITTED隔离级别下,SELECT会对读取的行/页加共享锁(S锁),正常情况下这些锁读完就释放,但如果查询慢到离谱(比如全表扫描),锁会持有很久,大大增加竞争概率; - 当查询扫描的数据量足够大时,SQL Server会把行级/页级的S锁升级为表级S锁,这会直接扩大锁的覆盖范围,更容易和其他操作(甚至是同一查询的并行线程)产生冲突;
- 你提到的
CXPACKET等待和自阻塞,大概率是并行查询的锅:同一个SELECT的多个工作线程在扫描数据、申请锁的过程中,可能出现循环等待(比如线程A拿着资源X等Y,线程B拿着Y等X),哪怕是同一个会话的不同线程,SQL Server的死锁检测机制也会判定这是死锁,直接把其中一个线程标记为牺牲品。
结合你补充的「加索引后性能恢复」,本质就是缺失索引导致查询执行效率极低,锁持有时间被无限拉长,再加上并行线程的内部资源竞争,最终触发了死锁。
具体排查&重现步骤
如果想深挖这个问题,可以按下面的步骤来操作:
捕获死锁的详细图形
- 先开跟踪:执行
DBCC TRACEON(1222, -1),这会把死锁的完整信息写入SQL Server错误日志; - 也可以用SQL Server Profiler(2008版本支持),选「Deadlock Graph」事件类,这样能直观看到死锁的资源、会话和语句关系,一眼就能看出是不是并行线程在互相卡。
- 先开跟踪:执行
分析查询的执行计划
- 单独跑存储过程里的SELECT语句,看实际执行计划:
SET SHOWPLAN_XML ON; 你的SELECT语句; - 重点盯两个点:有没有全表/聚集索引扫描(这就是缺失索引的实锤),有没有「Parallelism」运算符(说明启用了并行);
- 还可以查锁的情况:
SELECT * FROM sys.dm_tran_locks,如果看到resource_type = 'OBJECT'的锁,说明已经发生了锁升级。
- 单独跑存储过程里的SELECT语句,看实际执行计划:
排查CXPACKET等待的根源
- 当出现自阻塞时,查
sys.dm_os_waiting_tasks的blocking_session_id,如果阻塞者和当前会话ID一样,那就是并行线程内部在抢资源; - 你之前设
MAXDOP 1没解决,大概率是因为锁持有时间太长——哪怕串行执行,游标多次迭代反复申请锁,还是会有竞争窗口。可以试试调高并行度阈值:sp_configure 'cost threshold for parallelism', 50; RECONFIGURE;(默认是5,调高后小成本查询不会触发并行),或者给特定查询加OPTION(MAXDOP 1)。
- 当出现自阻塞时,查
验证索引的作用
- 查缺失索引建议:
SELECT * FROM sys.dm_db_missing_index_details,找到对应表的缺失索引; - 创建索引后再看执行计划,肯定会变成索引seek/scan,查询速度上去了,锁持有时间骤减,自然就没了死锁的土壤。
- 查缺失索引建议:
总结
你的情况完全符合逻辑:缺失索引拖慢了SELECT,锁持有的时间和范围都超标,再加上并行查询的线程内部竞争,最终触发了死锁。SELECT引发死锁一点都不奇怪,只要它拿的锁和其他资源(哪怕是自身线程的)形成循环等待,死锁就会发生。
内容的提问来源于stack exchange,提问作者George Menoutis
相关产品推荐
相关产品推荐

