SQL Server用与定义不匹配的数据填充PERSISTED列是否合法?


16

我正在跟踪有关计算列中的奇怪值的问题PERSISTED。那里的答案使人对这种行为的产生方式有一些猜测。

我在问以下问题:这不是一个彻底的错误吗?是否PERSISTED允许列以这种方式运行?

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

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 --shows impossible data

UPDATE @test SET Col1 = Col1*1 --"fix" the data by rewriting it

SELECT * FROM @test --observe fixed data

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

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

请注意,数据显示为“不可能”,因为计算列的值与其定义不对应。

众所周知,查询中的非确定性函数的行为可能会很奇怪,但是在这里,这似乎违反了持久化计算列的约定,因此应该是非法的。

插入随机数可能是人为的情况,但是如果我们要插入NEWID()值或SYSUTCDATETIME()呢?我认为这是一个相关问题,实际上可能会体现出来。

Answers:


9

这肯定是一个错误。col1值恰好是包含随机数的表达式的结果,这一事实显然不会改变正确值的col2假设。DBCC CHECKDB如果此操作针对永久表,则返回错误。

create table test (
    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));

DBCC CHECKDB

给出(对于我的测试运行,其中有“不可能”行)

Msg 2537, Level 16, State 106, Line 17
Table error: object ID 437576597, index ID 0, partition ID 72057594041008128, alloc unit ID 72057594046251008 (type In-row data), page (1:121), row 0. The record check (valid computed column) failed. The values are 2 and 0.
DBCC results for 'test'.
There are 5 rows in 1 pages for object "test".
CHECKDB found 0 allocation errors and 1 consistency errors in table 'test' (object ID 437576597).

它也报告说

repair_allow_data_loss是DBCC CHECKDB发现的错误的最低修复级别

而且,如果使用了修复选项,那么它会毫不客气地删除整行,因为它无法告知哪一列已损坏。

附加调试器表明,NEWID()正在对每个插入的行进行两次评估。一次在计算CASE表达式之前,一次在表达式内部。

在此处输入图片说明

可能的解决方法是使用

INSERT INTO @test
            (Col1)
SELECT ( ABS(CHECKSUM(NEWID()) % 5) )
FROM   (VALUES (1),(1),(1),(1),(1)) V(X); 

出于某种原因,它避免了该问题,并且每行仅对表达式求值一次。


2

在评论对话中,共识似乎是对OP的问题的答案是这确实构成了一个错误(即应该是非法的)。

OP引用了Vladimir Baranov对StackOverflow的分析,其中指出:

“第一次是Col1,第二次是持久化列的CASE语句。

在这种情况下,Optimiser不知道或不在乎NEWID是非确定性函数,并且将其调用两次。”

换句话说,应该期望[col1中的NEWID()]具有您刚插入的值与进行计算时相同的值。

这将与该错误的发生同义,该错误是为Col1创建NEWID,然后为持久列再次创建的:

INSERT INTO @Test (Col1, Contains2) VALUES
(NEWID(), CASE WHEN (NEWID()) LIKE '%2%' THEN 1 ELSE 0 END)

在我的测试中,其他不确定性函数(例如RAND和时间值)没有导致同一错误。

根据Martin的说法,该问题已提交给Microsoft(https://connect.microsoft.com/SQLServer/Feedback/Details/2751288),在此处有返回到此页面的注释以及StackOverflow分析(如下)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.