with(nolock)是sql server中允许脏读的查询提示,会读到未提交、重复、丢失甚至损坏的数据,因其跳过锁机制且不保证一致性。

WITH(NOLOCK) 是什么,为什么它会读到“不存在”的数据
WITH(NOLOCK) 是 SQL Server 中的查询提示(table hint),等价于 READ UNCOMMITTED 隔离级别。它让 SELECT 跳过共享锁,也不被其他事务的排他锁阻塞——听起来很爽,但代价是可能读到未提交的数据、重复行、丢失行,甚至崩溃时正在被修改的中间状态页。
常见错误现象包括:订单查出来两次、用户余额突然变负、报表总数对不上。这不是偶发 bug,而是设计行为——SQL Server 明确不保证一致性,只保证“能跑完”。
- 它不阻止其他事务删除/更新当前扫描的行,所以可能跳过某行(丢失)或重复读某行(因页拆分重定位)
- 如果另一事务正在
UPDATE一行并触发页拆分,WITH(NOLOCK)可能读到半写入的页,导致Msg 601或Msg 644错误 - 在有索引视图、计算列、XML 索引的表上,
WITH(NOLOCK)行为更不可控,某些版本会静默忽略该提示
什么时候真能用 NOLOCK,什么时候绝对不能碰
能用的前提不是“快”,而是业务允许脏读且后果可接受。典型场景只有两类:实时监控类只读看板(如服务器连接数趋势)、历史归档数据的批量导出(源表已停写)。
绝对禁用的场景:任何涉及金额、库存、状态流转、权限校验的查询。哪怕只是 SELECT COUNT(*) FROM orders WHERE status = 'paid',加了 WITH(NOLOCK) 就可能漏掉刚提交的支付单——因为那行还没来得及刷入索引页。
- 联接多个表时,只要其中一张用了
WITH(NOLOCK),整个查询都降级为READ UNCOMMITTED,别以为只影响那张表 - 在视图里硬编码
WITH(NOLOCK)很危险:调用方无法覆盖,后续加事务控制也无效 - SQL Server 2016+ 对内存优化表不支持
WITH(NOLOCK),会直接报错Msg 41317
比 NOLOCK 更安全的替代方案
想避免阻塞又不想脏读?优先考虑 READ COMMITTED SNAPSHOT(RCSI)。它开启后,所有普通 SELECT 自动读取行版本,既不阻塞写,也不被写阻塞,还保持事务一致性。
启用只需两步:ALTER DATABASE [YourDB] SET READ_COMMITTED_SNAPSHOT ON,再确认 is_read_committed_snapshot_on = 1(查 sys.databases)。注意:这会增加 tempdb 压力,且首次开启时需等待所有活动事务结束。
- 临时需要快查?用
SNAPSHOT隔离级别显式开启事务,比全局改 RCSI 更可控 - 纯统计类查询(如
COUNT(*))可考虑sys.dm_db_partition_stats的近似值,快且无锁 - 真要锁表又不想阻塞?
WITH (HOLDLOCK, UPDLOCK)比NOLOCK更明确表达意图,虽然慢但逻辑清晰
检查现有代码里有没有偷偷用 NOLOCK
很多人以为只在显式写 WITH(NOLOCK) 才算,其实 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 效果一样,而且影响整个批处理。
快速排查:在生产库执行以下语句,能扫出所有含 NOLOCK 或 READUNCOMMITTED 的缓存计划:
SELECT st.text, qp.query_plan FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp WHERE LOWER(st.text) LIKE '%nolock%' OR LOWER(st.text) LIKE '%readuncommitted%';
- 结果里如果出现应用框架自动生成的 SQL(比如 Entity Framework 的
AsNoTracking()在某些配置下会带NOLOCK),得倒查 ORM 层配置 - SSIS 包、SQL Agent 作业里的内嵌 SQL 容易被漏掉,要单独查
msdb.dbo.sysssispackages和sysjobsteps - 开发环境开了 RCSI 后,有些人会误以为可以删掉
WITH(NOLOCK)—— 实际上旧代码如果依赖脏读逻辑(比如故意跳过锁等待),删了反而出问题
真正难的不是语法怎么写,是判断“这一行数据,少读一次、多读一次、读错一次,业务能不能兜住”。很多线上事故不是因为不会加提示,而是没想清楚那个 WITH(NOLOCK) 到底在替谁担风险。










