NEWID() 和 PERSISTED 计算列的结果不一致

Inconsistent results with NEWID() and PERSISTED computed column

将 NEWID() 与持久计算列结合使用时,我得到了奇怪的结果。我是不是用错了某些功能?

在创建列时不使用持久化,因此在选择它们时计算值,将 return 更正值。 更新列 (col1) 也将 return 更正值。

DECLARE @test TABLE (
    Col1 INT,
    Contains2 AS CASE WHEN 2 IN (Col1) THEN 1 ELSE 0 END PERSISTED)

INSERT INTO @test (Col1) VALUES
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5)),
    (ABS(CHECKSUM(NEWID()) % 5))

SELECT * FROM @test
UPDATE @test SET Col1 = Col1*1
SELECT * FROM @test

/*
Col1    Contains2
2   0
2   0
0   1
4   0
3   0

Col1    Contains2
2   1
2   1
0   0
4   0
3   0
*/

显然,查询引擎为每一行计算了两次随机数。

第一次针对Col1,第二次针对持久化列的CASE语句。

Optimiser 不知道,或者在这种情况下不关心 NEWID 是一个非确定性函数并调用它两次。

其实,它甚至可能别无选择。你想让优化器在幕后创建一个临时的 table,用生成随机数的表达式的结果填充它的 Col1,然后读回那个临时的 table 并使用这些保存的中间结果用于计算 CASE 表达式的结果,然后执行最后的 INSERT?在这种情况下,优化器在不将中间结果写入磁盘的情况下计算两次表达式的成本更低。在其他一些情况下(例如,当您没有 5 行,而是 50 亿行或额外的索引时),估计的成本可能会有所不同,并且这种行为会发生变化。

我认为您无能为力。请注意这种行为。始终明确地将生成的一组随机数保存到 table,然后根据它们执行进一步的计算。

我在 SQL Server 2008 和 2014 中复制了它。 这是我在 SQL Server 2008 中获得的执行计划,但它并不是很有趣。 2014 年的计划是一样的,只是没有 Top 运算符。

Constant Scan算子输出一个Union1009列表,后面Compute Scalar用到。我想,这归结为 Constant Scan and/or Compute Scalar 运算符的实现细节。

观察到的行为告诉我们 newid() 在这里每行被调用两次。

在测试的时候,我去掉了与NEWID无关的函数,如果提前计算NEWID,则显示结果。可能对其他人有帮助。

DECLARE @test TABLE (
InsertType VARCHAR(30),
Col1 VARCHAR(5),
Contains2 AS CASE WHEN (Col1) LIKE '%2%' THEN 1 ELSE 0 END) --depends on Col1

INSERT INTO @test (InsertType, Col1) VALUES
    ('Compute With Insert', LEFT(NEWID(), 5)),
    ('Compute With Insert', LEFT(NEWID(), 5)),
    ('Compute With Insert', LEFT(NEWID(), 5)),
    ('Compute With Insert', LEFT(NEWID(), 5)),
    ('Compute With Insert', LEFT(NEWID(), 5))

SELECT * FROM @test

DECLARE @A VARCHAR(5) = LEFT(NEWID(), 5);
DECLARE @B VARCHAR(5) = LEFT(NEWID(), 5);
DECLARE @C VARCHAR(5) = LEFT(NEWID(), 5);
DECLARE @D VARCHAR(5) = LEFT(NEWID(), 5);
DECLARE @E VARCHAR(5) = LEFT(NEWID(), 5);

SELECT @A, @B, @C, @D, @E;

INSERT INTO @Test (InsertType, Col1) VALUES
('Compute Before Insert', @A), ('Compute Before Insert', @B), ('Compute Before Insert', @C), ('Compute Before Insert', @D), ('Compute Before Insert', @E)

SELECT * FROM @test

InsertType                 Col1        Contains2
Compute With Insert        C5507        0
Compute With Insert        C17D7        0
Compute With Insert        D9087        1
Compute With Insert        E2DB0        0
Compute With Insert        7D1AF        1
Compute Before Insert      31050        0
Compute Before Insert      2954C        1
Compute Before Insert      9E205        1
Compute Before Insert      DDF05        0
Compute Before Insert      ED708        0