SQL Server中WITH (NOLOCK)的SELECT在tempdb引发排他锁问题求解
问题分析与解决方案
核心原因拆解
- ASYNC_NETWORK_IO等待的本质:第一个查询虽加了
WITH (NOLOCK),但PHP遍历结果时未读完结果集,且事务始终处于打开状态,导致SQL Server无法释放本地临时表#ids_temp的锁——本地临时表的生命周期与连接/事务绑定,只要事务未提交或回滚,临时表的锁会一直被持有。 - NOLOCK未生效的关键:
NOLOCK(即READ UNCOMMITTED隔离级别)允许读取未提交数据,但不代表完全无锁。同一连接下,当第一个查询的结果集未被客户端处理完成时,SQL Server会持有临时表的排他锁(确保临时表元数据/数据页一致性),NOLOCK无法规避同一连接内的锁竞争。 - Sch-S锁的无关性:你提到的
grade_grades_history表的Sch-S锁确实不影响#ids_temp,但它属于同一事务内的资源,只要事务未结束,所有关联锁都会被保留。
无需修改代码的数据库配置调整
1. 全局设置事务隔离级别为READ UNCOMMITTED
将数据库默认隔离级别改为READ UNCOMMITTED,让所有查询默认启用NOLOCK行为:
ALTER DATABASE [你的数据库名称] SET READ_COMMITTED_SNAPSHOT OFF; ALTER DATABASE [你的数据库名称] SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
注意:该设置会让所有查询允许读取未提交数据,需确认业务可接受脏读风险。
2. 启用READ_COMMITTED_SNAPSHOT(推荐,低脏读风险)
若不想全局使用READ UNCOMMITTED,可开启READ_COMMITTED_SNAPSHOT,让默认的READ COMMITTED隔离级别通过行版本控制避免读锁阻塞:
ALTER DATABASE [你的数据库名称] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
此配置会让SQL Server为修改的数据生成版本,读取操作直接读取版本数据而非加锁,既避免锁阻塞,又不会产生脏读(读取的是已提交版本)。
3. 开启tempdb的快照隔离
调整tempdb配置,让临时表读取使用行版本控制,减少锁竞争:
ALTER DATABASE tempdb SET ALLOW_SNAPSHOT_ISOLATION ON;
额外说明
其他用户能正常运行该应用,大概率是其数据库已开启READ_COMMITTED_SNAPSHOT或默认隔离级别为READ UNCOMMITTED,与你的配置差异导致。
内容的提问来源于stack exchange,提问作者David Ball
相关产品推荐
相关产品推荐

