是否可以在单个SQL Server查询中产生幻像读取?

时间:2018-08-03 10:12:37

标签: sql-server tsql

我设法通过在一个事务中运行2条select语句(例如https://blobeater.blog/2017/10/26/sql-server-phantom-reads/)来找到幻象读取的所有说明

BEGIN TRAN

    SELECT #1

    DELAY DURING WHICH AN INSERT TAKES PLACE IN A DIFFERENT TRANSACTION

    SELECT #2

END TRAN

是否可以在一个select语句中重现幻像读取的内容?这意味着select语句从事务#1开始。然后,insert在事务#2上运行并提交。最后,事务1的select语句完成,但不返回事务2已插入的行。

2 个答案:

答案 0 :(得分:2)

SQL Server Transaction Isolation Levels documentation将一个幻像行定义为“符合搜索条件但没有最初看到”的幻像行(强调我)。因此,幻像读取需要一个以上的SELECT语句。

在执行SELECT语句执行期间插入的数据可能不会以READ COMMITTED隔离级别返回,具体取决于时间,但这不是定义上的幻象。下面的示例显示了此行为。

--create table with enough data for a long-running SELECT query
CREATE TABLE dbo.PhantomReadExample(
      PhantomReadExampleID int NOT NULL
        CONSTRAINT PK_PhantomReadExample PRIMARY KEY
    , PhantomReadData char(8000) NOT NULL
    );
--insert 100K rows
WITH 
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
    ,t1m AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a CROSS JOIN t1k AS b)
INSERT INTO dbo.PhantomReadExample WITH(TABLOCKX) (PhantomReadExampleID, PhantomReadData)
SELECT num*2, 'data'
FROM t1m
WHERE num <= 100000;
GO

--run this on connection 1
SELECT *
FROM dbo.PhantomReadExample
ORDER BY PhantomReadExampleID;
GO
--run this on connection 2 while the connection 1 SELECT is running
INSERT INTO dbo.PhantomReadExample(PhantomReadExampleID, PhantomReadData) 
    VALUES(1, 'data');
GO

SELECT查询扫描期间读取行时,将在行上获取共享锁,以确保仅读取已提交的数据,但是一旦读取数据,这些共享锁将立即释放,从而提高了并发性。这样可以在SELECT查询运行时,其他会话插入,更新和删除行。

在这种情况下,不会返回插入的行,因为有序的聚集索引扫描已经超过了插入点。

答案 1 :(得分:1)

下面是幻像读取的wikipedia definition

  

幻像读取发生在事务过程中的新行中   被另一个事务添加到正在读取的记录中。

     

当执行   SELECT ... WHERE操作。幻像读取异常是特殊的   事务1重复一个有范围的操作时不可重复读取的情况   SELECT ... WHERE查询,以及两个操作之间的事务2   在目标表中创建(即插入)新行(满足   该WHERE子句。

在单个读取查询中肯定可以重现(当然,还必须发生其他数据库活动才能产生幻像行)。

设置

CREATE TABLE Test(X INT PRIMARY KEY);

连接1 (保持运行状态)

SET NOCOUNT ON;
WHILE 1 = 1
INSERT INTO Test VALUES (CRYPT_GEN_RANDOM(4))

连接2

如果在已读提交的锁隔离级别(本地产品的默认值并通过下面的表提示实施)下运行,这极有可能返回一些行

WITH CTE AS
(
SELECT *
FROM Test WITH (READCOMMITTEDLOCK)
WHERE X BETWEEN 0 AND 2147483647
)
SELECT *
FROM CTE c1
FULL OUTER HASH JOIN CTE c2 ON c1.X = c2.X
WHERE (c1.X IS NULL OR c2.X IS NULL)

返回的行是在表的第一次读取和第二次读取之间添加的值,用于与WHERE X BETWEEN 0 AND 2147483647谓词匹配的行。