nolock能实现无锁读取,但仅跳过共享锁和更新锁,允许脏读、重复行、丢失行等;它不规避架构锁,对视图、函数、内存优化表无效,且需为每个表显式添加。

SELECT加NOLOCK提示是否真能实现无锁读取
可以,但必须明确:NOLOCK 是查询提示(table hint),不是隔离级别开关;它只对当前 SELECT 语句中指定的表生效,且仅跳过共享锁(S锁)和更新锁(U锁),不跳过架构锁或大容量更新锁。实际效果是允许脏读——可能读到未提交的数据、重复行、丢失行,甚至因页拆分导致的扫描错位。
常见错误现象:SELECT * FROM Orders WITH (NOLOCK) 执行时仍被阻塞,原因通常是目标表正被 ALTER TABLE 或 DROP INDEX 操作持有架构修改锁(Sch-M),而 NOLOCK 对 Sch-M 锁无效。
- 仅对
SELECT语句中的每个表单独添加WITH (NOLOCK),不能写在语句开头 - 若用表别名,提示必须紧跟别名后:
FROM Orders o WITH (NOLOCK),而非FROM Orders WITH (NOLOCK) o - 视图内定义的
NOLOCK不会自动继承,调用视图时仍需显式加提示
替代方案:使用READ UNCOMMITTED隔离级别更稳妥
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 的作用范围是整个会话(或显式事务),比逐个加 NOLOCK 更易维护,行为也更一致——它让所有后续 SELECT 都跳过 S/U 锁,包括 JOIN 中的关联表、子查询、CTE 引用的表。
但注意:该设置不会影响其他会话,也不会改变系统默认隔离级别;一旦执行 SET,后续所有 SELECT(除非显式覆盖)都按此级别运行,容易被遗忘导致意外脏读。
- 推荐在存储过程中使用,并在末尾恢复原级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED - 无法在函数(如标量函数、内联表值函数)中使用
SET语句 - SQL Server 2019 中,若数据库启用了
READ_COMMITTED_SNAPSHOT = ON,则READ UNCOMMITTED实际读取的是版本存储区快照,不再产生脏读,但依然跳过锁等待
哪些场景不该用NOLOCK或READ UNCOMMITTED
财务对账、库存扣减、状态机流转判断、任何需要强一致性校验的逻辑——这些场景下脏读直接导致业务错误,不是性能问题而是数据正确性问题。
典型误用:SELECT COUNT(*) FROM Sales WITH (NOLOCK) 统计销售单数用于报表,结果比实际少几千条,因为某些事务插入后回滚了,但 NOLOCK 已经读到了它们。
- 聚合函数(
COUNT、SUM、AVG)配合NOLOCK极易出错,因扫描可能跳过或重复计算同一行 - 涉及
ORDER BY+TOP N的分页查询,NOLOCK可能导致同一页数据在多次请求中顺序不一致,引发“幻页” - 当表上有启用
MEMORY_OPTIMIZED的表变量或临时表时,NOLOCK提示会被忽略,SQL Server 报错:Incorrect syntax near 'WITH'
验证是否真的避开了锁等待
最直接的方法是用另一个会话人为制造阻塞,再观察目标查询是否立即返回。例如:会话 A 执行 BEGIN TRAN; UPDATE Products SET Price = Price * 1.05 WHERE ID = 1;(不提交),会话 B 执行 SELECT * FROM Products WITH (NOLOCK) WHERE ID = 1; 应立刻返回旧值;而普通 SELECT 会卡住。
更可靠的方式是查动态管理视图:SELECT * FROM sys.dm_exec_requests WHERE blocking_session_id 0,确认目标查询的 session_id 不在阻塞链中;同时检查 sys.dm_tran_locks 中该会话持有的锁类型,应只有 Sch-S 锁,没有 KEY、PAGE 或 OBJECT 级别的 S 锁。
- 不要依赖 SQL Server Management Studio 的“执行计划”里有没有锁图标来判断——图形执行计划默认不显示锁信息
-
DBCC TRACEON(1200, -1)可输出锁申请日志,但仅限调试环境,生产库禁用 - 如果查询涉及多个数据库,
NOLOCK只对当前数据库内表生效,跨库引用仍需分别加提示
SQL Server 2019 中所谓“无锁读取”的本质,是主动放弃一致性保障来换取响应速度。真正难的不是语法怎么写,而是判断某次查询能否容忍一行数据多读一次、少读一次、或者读到回滚前的状态。










