Apakah legal untuk SQL Server untuk mengisi kolom PERSISTED dengan data yang tidak sesuai dengan definisi?

16

Saya menindaklanjuti pertanyaan ini tentang nilai aneh di PERSISTEDkolom yang dihitung. Jawaban di sana membuat beberapa tebakan tentang bagaimana perilaku ini terjadi.

Saya bertanya yang berikut: Apakah ini bukan bug langsung? Apakah PERSISTEDkolom pernah diizinkan berperilaku seperti ini?

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
*/

Perhatikan, bahwa data tampak "tidak mungkin" karena nilai kolom yang dihitung tidak sesuai dengan definisinya.

Telah diketahui secara umum bahwa fungsi non-deterministik dalam kueri dapat berperilaku aneh, tetapi di sini ini tampaknya melanggar kontrak kolom yang dikomputasi yang bertahan dan, oleh karena itu, harus ilegal.

Memasukkan angka acak mungkin merupakan skenario yang dibuat-buat tetapi bagaimana jika kita memasukkan NEWID()nilai atau SYSUTCDATETIME()? Saya pikir ini adalah masalah yang relevan yang mungkin bisa terwujud.

usr
sumber

Jawaban:

9

Ini tentu saja bug. Fakta bahwa col1nilai - nilai yang terjadi adalah hasil dari ekspresi yang melibatkan angka acak jelas tidak mengubah nilai col2yang seharusnya. DBCC CHECKDBmengembalikan kesalahan jika ini dijalankan terhadap tabel permanen.

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

Memberi (untuk uji coba saya yang memiliki satu baris "tidak mungkin")

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).

Itu juga melaporkan itu

repair_allow_data_loss adalah tingkat perbaikan minimum untuk kesalahan yang ditemukan oleh DBCC CHECKDB

Dan jika diambil pada opsi perbaikan tanpa basa-basi menghapus seluruh baris karena tidak memiliki cara untuk mengetahui kolom mana yang rusak.

Melampirkan debugger menunjukkan bahwa NEWID()sedang dievaluasi dua kali per baris yang dimasukkan. Sekali sebelum CASEekspresi dievaluasi dan sekali di dalamnya.

masukkan deskripsi gambar di sini

Solusi yang mungkin untuk digunakan

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

Yang karena satu dan lain alasan menghindari masalah dan hanya mengevaluasi ekspresi sekali per baris.

Martin Smith
sumber
2

Per percakapan komentar, tampaknya konsensus bahwa jawaban untuk pertanyaan OP adalah bahwa ini memang merupakan bug (yaitu harus ilegal).

OP merujuk analisis Vladimir Baranov tentang StackOverflow, di mana mereka menyatakan:

"Pertama kali untuk Col1, kedua kalinya untuk pernyataan KASUS dari kolom yang ada.

Pengoptimal tidak tahu, atau tidak peduli dalam hal ini bahwa NEWID adalah fungsi non-deterministik dan menyebutnya dua kali. "

Dengan kata lain, harus diharapkan bahwa [NEWID () dalam] col1 memiliki nilai yang sama seperti yang Anda masukkan saat membuat perhitungan.

Ini akan identik dengan apa yang terjadi dengan bug, di mana NEWID dibuat untuk Col1 dan kemudian dibuat lagi untuk kolom yang ada:

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

Dalam pengujian saya, fungsi non-deterministik lainnya seperti RAND dan nilai waktu tidak menghasilkan bug yang sama.

Per Martin, ini telah dinaikkan ke Microsoft ( https://connect.microsoft.com/SQLServer/Feedback/Details/2751288 ) di mana ada komentar kembali ke halaman ini dan analisis StackOverflow (di bawah).

John
sumber