Serializable 隔离级别的意外行为

Unexpected behaviour of the Serializable isolation level

测试设置

我有一个 SQL Server 2014 和一个简单的 table MyTable,其中包含列 Code (int)Data (nvarchar(50)),没有为此创建索引 table。

我在 table 中有 4 条记录,方式如下:

1, First
2, Second
3, Third
4, Fourth

然后我运行在事务中进行以下查询:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRANSACTION

DELETE FROM dbo.MyTable 
WHERE dbo.MyTable.Code = 2

我有一个受影响的行,但我没有发出提交或回滚。

接下来我开始另一笔交易:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION

SELECT TOP 10 Code, Data
  FROM dbo.MyTable
  WHERE Code = 3

在这一步,带有 SELECT 查询的事务挂起,等待带有 DELETE 查询的事务完成。

我的问题

我不明白为什么带有 SELECT 查询的事务正在等待带有 DELETE 查询的事务。据我了解,删除的行(代码 2)与选定的行(代码 3)无关,据我了解隔离级别 SERIALIZABLE SQL 服务器的具体情况 SQL 服务器不应锁定整个 table 在这种情况下。也许发生这种情况是因为 SERIALIZABLE 的最小锁定量是一个页面?如果 table 有更多行,比如 1000000(那么其他页面的一些行不会被锁定),那么它可能会产生不一致的行为来从其他页面选择行。请帮助弄清楚为什么在我的情况下会发生锁定。

在锁定 READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE 下,SELECT 查询必须为查询计划实际读取的每一行放置共享 (S) 锁。锁可以放置在行级、页级或 table 级。此外,SERIALIZABLE 将在范围上放置锁,以便在持有锁时没有其他会话可以插入匹配的行。

并且因为您有 "no indexes created for this table",此查询:

SELECT TOP 10 Code, Data
  FROM dbo.MyTable
  WHERE Code = 3

必须执行 table 扫描,并且它必须读取所有行(甚至那些代码为 2 的行)以确定它们是否符合 SELECT.

这就是为什么您应该几乎总是使用 Row-Versioning 的原因之一,方法是将数据库设置为 READ COMMITTED SNAPSHOT,或者对只读事务进行编码以使用 SNAPSHOT 隔离。