Ste*_*eve -1 sql-server transaction isolation-level
前几天我在 StackOverflow 上遇到了这个问答。引用问题作者的评论,情况似乎“有些可怕”。本次问答中也设置了类似的情况。
我的经验法则一直是 SQL 中的单个语句“原子地”执行。也就是说,在任何默认配置下,成功执行的单个语句(即没有死锁或中止)不存在任何潜在的并发问题。
然而我链接的两个问答表明并非如此。
说明问题的最基本场景似乎是这种自连接:
SELECT
t1.*
,t2.*
FROM
some_table AS t1
FULL OUTER JOIN
some_table AS t2
ON t2.id = t1.id
Run Code Online (Sandbox Code Playgroud)
是否存在不需要t1与 完全一致的情况t2,因此此查询可能会在全连接的一侧或另一侧返回 NULL,因为 t1 的数据与 t2 的数据是在不同的时间获取的因此每个可能引用不同的数据(即使它是在单个语句中引用的同一个表)?
我的假设始终是单个语句(引用多个表,或多次引用某些表)在某个时间步骤获取它所需的所有数据,作为数据库的单个一致快照。如果隔离级别为READ COMMITTED,那么我假设快照是单个语句开始时所有已提交数据的快照。
我对 RCSI 功能的理解是,它将这种假定的快照保证扩展到多语句事务。
我很震惊(虽然并不完全惊讶)发现事实似乎并非如此,而且我多年来的基本理解似乎被颠覆了。
有关这方面的信息似乎非常少。正如2014 年这篇文章的作者所说,这种行为似乎“甚至能够误导经验丰富的数据库从业者”。
该文章表明,即使是非常常见的 SELECT 查询样式似乎也能够在执行过程中修改其基础数据,并返回违反 WHERE 子句的显式逻辑标准的结果。这种行为方式可能成为默认行为,这似乎很荒谬。
问题
Microsoft(或 SQL 标准)是否有任何信息明确指定并确认此行为?
为了保证单个语句相当于所有数据的单个一致快照,需要进行哪些设置(在会话级别或语句级别)?
您提出了多个问题 - 下面回答了其中一些问题,但我不会为您找到来自 Microsoft 或 SQL 标准的链接。
是否有什么情况下t1不需要与t2完全一致
是的。
这很容易演示
SET NOCOUNT ON;
DROP TABLE IF EXISTS some_table
CREATE TABLE some_table (id int);
INSERT some_table
SELECT value
FROM generate_series(1, 100000)
DECLARE @I int = 100000
WHILE @I <= 2000000
BEGIN
INSERT some_table VALUES (@I);
SET @I = @I + 1
END
Run Code Online (Sandbox Code Playgroud)
(WITH (READCOMMITTEDLOCK)如果你知道你肯定没有启用RCSI,你可以删除它)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
--SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT
t1.*
,t2.*
FROM
some_table AS t1 WITH (READCOMMITTEDLOCK)
FULL OUTER JOIN
some_table AS t2 WITH (READCOMMITTEDLOCK)
ON t2.id = t1.id
WHERE t1.id IS NULL OR t2.id IS NULL
OPTION (MAXDOP 1)
Run Code Online (Sandbox Code Playgroud)
对我来说,我只需要启动“连接 1”运行并跳到另一个窗口,并在“连接 2”中运行查询一次,然后才能获得结果。
SQL Server 选择了哈希联接,行计数清楚地表明,自从读取t1实例的表并构建哈希表以来,其他行已添加到基础表并由实例读取t2。
我对 RCSI 功能的理解是,它将这种假定的快照保证扩展到多语句事务。
这种理解是错误的。RCSI 功能为单个语句提供快照保证。完整的SNAPSHOT隔离级别将其扩展到多个语句。
为了保证单个语句相当于所有数据的单个一致快照,需要进行哪些设置(在会话级别或语句级别)?
任何一个快照隔离级别都可以保证这一点。对于悲观的隔离级别只能SERIALIAZBLE保证这一点。
上面的代码的行为也相同REPEATABLE READ- 虽然这会锁定扫描期间读取的行t1并保留锁,但这不会阻止添加和读取其他行t2(可重复读取变体可能会将其行/页锁升级到表)锁,因此最终会阻止插入,但这并不能保证,并且在我的实例上的几次尝试运行中我没有看到这种情况成功发生 - 如果您确实遇到这种情况,您可能需要多次运行“连接 2”。请确保“连接 1”仍显示为“正在执行”(如果这样做)。