欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > sql server中的with 锁各种区别

sql server中的with 锁各种区别

2025/6/22 18:42:00 来源:https://blog.csdn.net/u010042660/article/details/148802645  浏览:    关键词:sql server中的with 锁各种区别

📘 SQL Server 常用 WITH (Hint) 用法与组合场景对照表

Hint 组合作用说明常见用途是否阻塞他人是否读脏数据备注
WITH (NOLOCK)不加共享锁,允许读取未提交数据报表导出、大数据分页等价于 READ UNCOMMITTED,脏读风险高
WITH (HOLDLOCK)保持共享锁直到事务结束(默认读锁是读完就释放)需要防止幻读、读取一致性要求高✅(读锁)类似 SERIALIZABLE,读一致性好
WITH (UPDLOCK)读取时申请更新锁,防止其他事务更新“先查后改”场景,避免并发写冲突✅(写锁)常用于检查数据是否存在后决定更新插入
WITH (XLOCK)独占锁,阻止其他事务读写强一致性要求操作(如删除、强制改)✅(全锁)不建议长时间持有,影响并发
WITH (TABLOCK)表级共享锁,锁住整张表全表扫描、导出✅(整表)读期间禁止别人写
WITH (TABLOCKX)表级独占锁,整表禁止读写初始化数据、清空、批量更新✅(强封锁)通常配合事务短时间使用
WITH (ROWLOCK)强制使用行锁,避免锁升级为页/表锁高频并发插入、更新⚠️ 取决于语句仅做锁粒度建议,SQL Server 可忽略
WITH (PAGLOCK)尽量使用页锁,适合中等范围更新分批更新大量记录⚠️ 取决语句少见,不如 ROWLOCK 常用
WITH (INDEX(IndexName))强制使用某个索引执行计划优化器走错索引时手动纠正不影响锁行为不影响锁,仅影响执行路径
WITH (FORCESEEK)强制走索引查找路径优化器自动选择表扫描导致性能低不影响锁行为通常用于调优大型表的慢查询
WITH (FORCESCAN)强制走全表扫描与 FORCESEEK 相反不影响锁行为不推荐使用,除非确认 seek 更慢
WITH (NOEXPAND)不展开视图,直接使用物化索引视图使用 Indexed View 时保持优化器使用索引不影响锁行为仅对物化视图有效,需特权支持
WITH (READCOMMITTED)强制使用已提交读显式指定默认隔离级别和默认行为一致
WITH (READUNCOMMITTED)等价于 NOLOCK,允许脏读报表、快照、对一致性容忍的数据访问用法不同但含义一致

✅ 常见 Hint 组合推荐(按场景)

组合 Hint用途场景描述说明
WITH (NOLOCK)报表分页、大量读取快速不阻塞,但可能脏读
WITH (UPDLOCK, HOLDLOCK)先查再更避免“查完数据被别人改了”
WITH (ROWLOCK, UPDLOCK)并发更新,防止锁冲突精细控制锁粒度,避免锁升级
WITH (XLOCK, ROWLOCK)强制对每行加独占锁限制读写并发,适合关键操作
WITH (TABLOCKX)初始化、清空整表封锁全表,强一致但需快速提交事务
WITH (INDEX(...), NOLOCK)快速读取指定索引加快查询且不加锁,脏读风险存在
WITH (FORCESEEK, HOLDLOCK)精确读取 + 保证一致性强制走索引 + 锁住读取数据
WITH (NOEXPAND)保持 Indexed View 性能避免视图被展开为普通查询

⚠️ 使用注意事项

  • 多个 Hint 用英文逗号隔开,如:
    SELECT * FROM MyTable WITH (UPDLOCK, HOLDLOCK, ROWLOCK)

先查后改 — 避免“幻读”和脏写

BEGIN TRAN;SELECT Quantity
FROM Inventory WITH (UPDLOCK, HOLDLOCK)
WHERE ProductID = 1001;-- 假设库存充足,执行扣减
UPDATE Inventory
SET Quantity = Quantity - 1
WHERE ProductID = 1001;COMMIT TRAN;

批量删除数据时锁表保护

BEGIN TRAN;DELETE FROM Orders WITH (TABLOCKX)
WHERE OrderDate < '2020-01-01';COMMIT TRAN;

强制使用索引加快查询

SELECT OrderID, OrderDate
FROM Orders WITH (INDEX(IX_Orders_OrderDate))
WHERE OrderDate > '2025-01-01';

控制锁粒度,避免锁升级

UPDATE Products WITH (ROWLOCK, UPDLOCK)
SET Stock = Stock - 10
WHERE ProductID = 1234;

查询某条记录时保持锁直到事务结束,防止脏写

BEGIN TRAN;SELECT * FROM Accounts WITH (HOLDLOCK, ROWLOCK)
WHERE AccountID = 5678;-- 处理业务逻辑UPDATE Accounts
SET Balance = Balance - 100
WHERE AccountID = 5678;COMMIT TRAN;

复杂联表查询时,指定索引和读未提交

SELECT o.OrderID, c.CustomerName
FROM Orders o WITH (NOLOCK, INDEX(IX_Orders_CustomerID))
JOIN Customers c WITH (NOLOCK)
ON o.CustomerID = c.CustomerID
WHERE o.OrderDate > '2025-01-01';

防止死锁,锁定行用于更新

BEGIN TRAN;SELECT * FROM Inventory WITH (UPDLOCK, ROWLOCK)
WHERE ProductID = 789;-- 执行更新
UPDATE Inventory
SET Quantity = Quantity - 5
WHERE ProductID = 789;COMMIT TRAN;
示例编号场景描述使用 Hint
1报表快速查询NOLOCK
2库存扣减防止超卖UPDLOCK, HOLDLOCK
3批量删除防止并发访问TABLOCKX
4索引优化查询INDEX(索引名)
5精确锁粒度控制ROWLOCK, UPDLOCK
6账户资金变更防脏写HOLDLOCK, ROWLOCK
7联表查询+索引+不阻塞NOLOCK, INDEX(...)
8防止更新死锁UPDLOCK, ROWLOCK

为什么用多个锁提示?比如 UPDLOCK + HOLDLOCK

UPDLOCK的作用
只在读取这条数据时加更新锁(一种介于共享锁和排他锁的锁)。

更新锁允许其他事务读取,但阻止其他事务获得写锁。

这防止了两个事务同时读取后,都去更新数据造成冲突。

HOLDLOCK的作用
默认情况下,锁在语句结束时释放(例如,SELECT 语句读完马上释放共享锁或更新锁)。

加了 HOLDLOCK,会将锁保持到整个事务结束,防止其他事务在当前事务未提交前修改数据。

这样可以避免“不可重复读”和“幻读”问题。

BEGIN TRAN;SELECT Quantity
FROM Inventory WITH (UPDLOCK, HOLDLOCK)
WHERE ProductID = 1001;-- 这里做业务判断,比如库存是否足够
IF @quantity > 0
BEGINUPDATE InventorySET Quantity = Quantity - 1WHERE ProductID = 1001;
ENDCOMMIT TRAN;

如果只有 UPDLOCK,SELECT 语句结束后,锁就释放了,后续操作可能会被其他事务修改。

如果只有 HOLDLOCK,它是共享锁,会阻止写,但无法阻止其他事务读取并加更新锁,存在写写冲突风险。

两者结合,读的时候加更新锁,且保持锁直到事务结束,保证了安全性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词