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
BEGIN
    UPDATE Inventory
    SET Quantity = Quantity - 1
    WHERE ProductID = 1001;
END

COMMIT TRAN;

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

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

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

你可能感兴趣的:(java,开发语言)