SQL Server 中的键范围 RangeI-N 锁兼容性

use*_*580 9 sql-server transaction locking

所述文档的状态,该RangeI-N锁(插入范围,空资源锁;插入新钥匙插入的索引之前用于测试范围)是用本身兼容(参见兼容性矩阵),因此,即使一个事务已获得的RangeI -N lock on特定键另一个事务也可以获得这样的锁。

在下面,它说

在事务中插入值时,在执行插入操作的事务期间不必锁定值落入的范围。锁定插入的键值直到事务结束就足以保持可序列化性。例如,给定这个 INSERT 语句:

INSERT mytable VALUES ('Dan');

RangeI-N 模式键范围锁放置在与名称 David 相对应的索引条目上以测试范围。如果锁定被授予,则插入 Dan 并对值 Dan 放置一个排它 (X) 锁。RangeI-N 模式键范围锁仅在测试范围时才需要,在执行插入操作的事务期间不会保持。其他事务可以在插入值 Dan 之前或之后插入或删除值。但是,任何尝试读取、插入或删除值 Dan 的事务都将被锁定,直到插入事务提交或回滚。

引用另一个来源- Microsoft SQL Server 2008 Internals: Transactions and Concurrency:

例如,当 SQL Server 尝试使用 Serializable 隔离将会话中的键之间的范围插入时,将获取 RangeIn-Null 锁。这种类型的锁并不常见,因为它通常非常短暂。直到找到正确的插入位置,它才会被持有,然后该锁被转换为 X 锁。

我的理解是这种类型的锁是在识别新插入的钥匙应该放置的范围的过程中(我假设这就是“测试范围”的意思)。发生这种情况后,锁被释放,新的钥匙被插入,并在其上放置一个 X 锁。

但是我不明白为什么说两个 RangeI-N 锁是相互兼容的。如果事务 A 和 B 都在同一个键上放置 RangeI-N 锁,因为他们都想在范围中插入一个新键并且事务 A 先执行插入,那么由 B 确定的键插入位置可能已经不正确,因为范围发生了变化(A 在那里插入了一个新值)。有人能解释一下吗?

i-o*_*one 4

我的理解是,这种类型的锁是在识别新插入的密钥应放置的范围的过程中(我假设这就是“测试范围”的含义)。发生这种情况后,锁被释放,新钥匙被插入,并在其上放置 X 锁。

RangeI-N请求锁定是为了测试插入键范围不会干扰另一个事务,该事务可能会执行一些与插入不兼容的可序列化操作(例如读取)。

在将记录插入索引期间,数据库引擎定位页面,其中应放置要插入的键。此阶段尚未使用页锁和键锁,仅使用锁存器。一旦找到页面,引擎就开始获取执行向页面插入记录所需的锁。这些是IX对所定位页面的锁定,然后RangeI-N是对正在插入的密钥旁边的密钥的锁定。获取后RangeI-N不保留,并X请求正在插入的密钥的下一个锁定。获取必要的锁后,将继续插入记录。

假设我们有表和数据

CREATE TABLE T
(
    K int NOT NULL,
    CONSTRAINT UQ_T UNIQUE CLUSTERED (K)
);

INSERT INTO T (K)
VALUES (1), (2), (9), (10);
Run Code Online (Sandbox Code Playgroud)

让我们获取以下查询的结果

SELECT K, %%lockres%% AS lockres
FROM T;
Run Code Online (Sandbox Code Playgroud)

这是

CREATE TABLE T
(
    K int NOT NULL,
    CONSTRAINT UQ_T UNIQUE CLUSTERED (K)
);

INSERT INTO T (K)
VALUES (1), (2), (9), (10);
Run Code Online (Sandbox Code Playgroud)

(我们会在后面的内容中提到)。

然后,假设我们有一个查询

SELECT COUNT(*)
FROM T
WHERE K BETWEEN 1 AND 9;
Run Code Online (Sandbox Code Playgroud)

在隔离级别下运行它SERIALIZABLE会产生以下锁定足迹

SELECT 锁定足迹

为了保护键范围,数据库引擎使用RangeS-S锁锁定该范围内的每个键。此外,范围旁边的键被锁定(这对于非唯一索引是必要的,但引擎也为唯一索引执行此操作)。在这种情况下,锁被放置在与键 1、2、9 和 10 对应的资源上(根据之前查询的结果)。

非并发插入表

INSERT INTO T (K)
VALUES (3);
Run Code Online (Sandbox Code Playgroud)

结果如下锁定足迹

INSERT 锁定足迹

表中不存在键 3,因此数据库引擎请求RangeI-N锁定与键 9 对应的资源(30b7763ed433),此时该资源紧邻 3。获取RangeI-N意味着不存在不兼容的可序列化活动正在执行,因此引擎继续并获取与键 3 相对应的X资源(98ec012aa510)的锁。然后发生实际插入并释放锁。请注意,没有释放RangeI-N锁。

在上面运行SELECTINSERT并发导致INSERT等待,直到执行可序列化的事务SELECT处于活动状态

SELECT 和 INSERT 并发

因为RangeI-N不兼容RangeS-S。一旦交易执行SELECT结束,INSERT就会继续进行。

这就是“测试范围”的意思。


但是我不明白为什么两个 RangeI-N 锁据说是相互兼容的。

RangeI-N锁兼容性很好,因为它允许并发插入键范围。

想象一下我们正在做的

INSERT INTO T (K)
VALUES (4);
Run Code Online (Sandbox Code Playgroud)

INSERT INTO T (K)
VALUES (6);
Run Code Online (Sandbox Code Playgroud)

同时。

可能会发生在某个时刻两个语句都获取RangeI-N与键 9 相对应的资源(30b7763ed433)。但这没关系,因为并发性是由X键和页锁存器进一步控制的。