SQL Server不使用间隙锁,其“类似间隙锁的阻塞”实为SERIALIZABLE隔离级别下的键范围锁(如RangeS-S、RangeI-N),仅在此级别启用,且依赖执行计划与索引覆盖;默认READ COMMITTED无此锁。

SQL Server 本身不使用间隙锁(Gap Lock)——这是 MySQL InnoDB 特有的机制。你在 SQL Server 中遇到的“类似间隙锁的阻塞”,实际是键范围锁(Key-Range Lock)或索引区间上的意向锁,在可序列化(SERIALIZABLE)隔离级别下才会启用。
为什么 SQL Server 的 JOIN 插入会卡住,却不是间隙锁?
SQL Server 在 SERIALIZABLE 隔离级别下会对查询涉及的索引键范围加 RangeS-S(共享范围锁)或 RangeI-N(插入意向范围锁),防止幻读。但这种锁只在显式设为 SERIALIZABLE 时才激活;默认的 READ COMMITTED 不会产生这类范围锁。
- 如果你看到 INSERT 被阻塞且
sys.dm_tran_locks显示resource_type = 'KEY'+resource_description包含范围(如(8194443284a0)),那大概率是事务用了SERIALIZABLE - JOIN 操作本身不触发范围锁,但若 JOIN 条件字段无索引,SQL Server 可能退化为表扫描 + 大量行锁,间接扩大阻塞面
- 真正卡住 INSERT 的,往往是另一个未提交事务对同一张表做了 SERIALIZABLE 查询(比如
SELECT ... FROM t1 JOIN t2 ON ... WHERE ... FOR UPDATE等效逻辑)
如何确认当前是不是 SERIALIZABLE 引发的范围锁?
执行以下查询,看是否有活跃事务启用了序列化:
SELECT
session_id,
transaction_isolation_level,
CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'Read Uncommitted'
WHEN 2 THEN 'Read Committed'
WHEN 3 THEN 'Repeatable Read'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
END AS isolation_desc,
status,
command,
sql_handle
FROM sys.dm_exec_requests
WHERE transaction_isolation_level = 4;
如果返回结果非空,说明有事务正以 SERIALIZABLE 运行 —— 这就是你“疑似间隙锁”的源头。
- 别直接查
sys.dm_tran_locks锁类型,先定位事务级别,否则容易误判 -
READ COMMITTED SNAPSHOT(RCSI)开启后,即使事务用READ COMMITTED,也不会阻塞读写,但不会影响SERIALIZABLE行为
INSERT + JOIN 场景下怎么避免范围锁干扰?
核心原则:让 JOIN 和 INSERT 尽量不共用 SERIALIZABLE 上下文,同时确保索引覆盖 JOIN 条件。
- 把 JOIN 查询和 INSERT 拆成两个独立事务:先用
READ COMMITTED做只读 JOIN 获取必要 ID,再用新事务做 INSERT —— 避免在 SERIALIZABLE 下跨操作持锁 - 确保所有 JOIN 字段都有合适索引:例如
orders.user_id和users.id都必须是主键或有非空唯一索引,否则 SQL Server 可能走哈希匹配或嵌套循环并锁住大量中间结果 - 禁用不必要的 SERIALIZABLE:检查应用层连接字符串、存储过程开头是否写了
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE,删掉它 - 如业务真需幻读防护,改用
SNAPSHOT隔离级别:它通过版本存储实现一致性读,不加范围锁,也不阻塞 INSERT
容易被忽略的一点
SQL Server 的“范围锁”行为高度依赖执行计划。同一个 INSERT ... SELECT ... JOIN 语句,在不同数据分布、统计信息陈旧、或参数嗅探失效时,可能从索引查找退化为扫描,从而突然开始申请大范围锁。所以不能只看隔离级别,还要定期更新统计信息、用 OPTION (RECOMPILE) 防止计划固化。










