SQL Server UPDATE默认先加更新锁(U锁)再升级为排他锁(X锁),锁粒度由优化器决定:索引命中则行级(RID/KEY),否则可能降级为页锁(PAG)或表锁(TAB);超5000行或内存压力大时自动锁升级。

SQL Server 的 UPDATE 默认就倾向使用行级锁,但是否真能锁住“行”,取决于索引、查询条件和数据分布——不是加个 ROWLOCK 就万事大吉。
UPDATE 默认用什么锁?为什么有时会升级成页锁或表锁?
UPDATE 语句执行时,SQL Server 会先尝试加更新锁(U 锁),再升级为排他锁(X 锁)。锁粒度由查询优化器决定:如果 WHERE 条件能精准命中索引键(比如主键或唯一索引列),且该索引是聚集索引或覆盖足够字段,它大概率锁 RID 或 KEY(即行级);否则可能退化为 PAG(页锁)甚至 TAB(表锁)。
常见触发锁升级的场景包括:
- WHERE 条件未走索引(全表扫描 → 太多行锁 → 自动升级)
- 更新大量行(默认阈值约 5000 行,具体受内存与版本影响)
- 表无主键/无有效索引,导致无法精确定位单行
- SQL Server 2019+ 启用了“优化锁定”(Optimized Locking),但该功能只减少锁数量,不改变升级逻辑
如何用 ROWLOCK 提示强制行级锁?
ROWLOCK 是表提示(table hint),必须显式写在 UPDATE 语句的目标表后,作用是“告诉优化器:请尽可能用行锁,别升到页或表”。但它不是强制保证,而是一种倾向性建议。
正确写法示例:
UPDATE ls1 WITH (ROWLOCK) SET f_status = 'sold' WHERE f_account_id = 37;
注意点:
- 仅对目标表生效,
FROM子句中的其他表不受影响 - 不能单独使用 —— 单独
WITH (ROWLOCK)在 UPDATE 中意义有限;搭配UPDLOCK更稳妥(见下一条) - 若底层存储引擎判断行锁开销过大(如锁内存超限),仍可能忽略该提示并升级
为什么推荐搭配 UPDLOCK 使用?
单纯 ROWLOCK 只控制锁粒度,不控制锁类型;而 UPDLOCK 能确保你在读取阶段就持有更新锁,避免“先 SELECT 再 UPDATE”中间被其他事务篡改数据,也减少死锁概率。
典型安全模式(尤其用于秒杀类逻辑):
BEGIN TRANSACTION; SELECT * FROM ls1 WITH (ROWLOCK, UPDLOCK) WHERE f_account_id = 37; -- 此时该行已被 U 锁保护,其他事务无法获取 X 锁或再次加 U 锁 UPDATE ls1 SET f_status = 'sold' WHERE f_account_id = 37; COMMIT;
关键细节:
-
UPDLOCK+ROWLOCK组合,才能真正模拟 Oracle 的SELECT ... FOR UPDATE - 必须在同一个事务中完成 SELECT 和 UPDATE,否则 U 锁在 SELECT 结束后释放
- 如果 WHERE 条件返回多行,每行都持 U 锁,务必控制范围,否则易引发阻塞
容易踩的坑:ROWLOCK 不是银弹
很多人以为加了 ROWLOCK 就高枕无忧,结果线上出现大面积阻塞或锁升级,根本原因常被忽略:
- 没确认执行计划是否真的走了索引 —— 看
EXEC sp_lock @@spid输出里的Type列:出现RID或KEY才算行级;PAG或TAB就失败了 - 在非聚集索引上更新非键列,可能触发“书签查找”并锁住聚集索引行 + 非聚集索引行,锁数量翻倍
- SQL Server 2022+ 对锁内存更敏感,
ROWLOCK在高并发小事务中反而可能因锁管理开销升高 CPU - 从 SQL Server 2019 开始,“优化锁定”默认开启,它会合并相邻行锁以节省内存 —— 这不是锁升级,但行为上类似,需用
sys.dm_tran_locks查看实际锁数量
真正可靠的行级控制,靠的不是提示词堆砌,而是索引设计 + 事务范围收敛 + 锁监控闭环。否则,ROWLOCK 只是一张薄纸,风一吹就破。










