เป็นเรื่องถูกกฎหมายหรือไม่ที่ 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()? ฉันคิดว่านี่เป็นปัญหาที่เกี่ยวข้องซึ่งอาจแสดงให้เห็นจริง

คำตอบ:


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 ของคอลัมน์ที่คงอยู่

เครื่องมือเพิ่มประสิทธิภาพไม่ทราบหรือไม่สนใจในกรณีนี้ NEWID เป็นฟังก์ชันที่ไม่ได้กำหนดค่าไว้และเรียกมันว่าสองครั้ง "

อีกวิธีหนึ่งคือควรคาดว่า [NEWID () ภายใน] col1 มีค่าเดียวกับที่คุณเพิ่งใส่เมื่อคุณทำการคำนวณ

นี่จะเป็นสิ่งที่ตรงกันกับสิ่งที่เกิดขึ้นกับบั๊กที่ซึ่ง NEWID ถูกสร้างขึ้นสำหรับ Col1 และจากนั้นสร้างอีกครั้งสำหรับคอลัมน์ที่ยังคงอยู่:

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 (ด้านล่าง)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.