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 隔离。
测试设置
我有一个 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 隔离。