È legale che SQL Server riempia le colonne PERSISTED con dati che non corrispondono alla definizione?


16

Sto seguendo questa domanda sugli strani valori in una PERSISTEDcolonna calcolata. La risposta lì fa alcune ipotesi su come questo comportamento è diventato.

Sto chiedendo quanto segue: questo non è un vero e proprio bug? Le PERSISTEDcolonne sono mai autorizzate a comportarsi in questo modo?

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

Si noti che i dati sembrano "impossibili" perché i valori della colonna calcolata non corrispondono alla sua definizione.

È noto che le funzioni non deterministiche nelle query possono comportarsi in modo strano, ma qui ciò sembra violare il contratto di colonne calcolate persistenti e, pertanto, dovrebbe essere illegale.

L'inserimento di numeri casuali potrebbe essere uno scenario forzato, ma cosa accadrebbe se stessimo inserendo NEWID()valori o SYSUTCDATETIME()? Penso che questo sia un problema rilevante che potrebbe manifestarsi praticamente.

Risposte:


9

Questo è certamente un bug. Il fatto che i col1valori risultino essere il risultato di un'espressione che coinvolge numeri casuali non cambia chiaramente quale col2dovrebbe essere il valore corretto . DBCC CHECKDBrestituisce un errore se questo viene eseguito su una tabella permanente.

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

Dà (per la mia prova che aveva una riga "impossibile")

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

Lo riporta anche

repair_allow_data_loss è il livello di riparazione minimo per gli errori rilevati da DBCC CHECKDB

E se preso sull'opzione di riparazione cancella senza tante cerimonie l'intera riga in quanto non ha modo di dire quale colonna è danneggiata.

Allegare un debugger mostra che NEWID()viene valutato due volte per riga inserita. Una volta prima che l' CASEespressione venga valutata e una volta al suo interno.

inserisci qui la descrizione dell'immagine

Una possibile soluzione alternativa potrebbe essere quella di utilizzare

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

Che per un motivo o per l'altro evita il problema e valuta l'espressione solo una volta per riga.


2

Secondo la conversazione di commento, il consenso sembra essere che la risposta alla domanda del PO è che ciò costituisce un bug (cioè dovrebbe essere illegale).

L'OP fa riferimento all'analisi di Vladimir Baranov su StackOverflow, in cui si afferma:

"Prima volta per Col1, seconda volta per l'istruzione CASE della colonna persistente.

L'ottimizzatore non sa o non si preoccupa in questo caso che NEWID sia una funzione non deterministica e la chiama due volte. "

Detto in altro modo, ci si dovrebbe aspettare che [il NEWID () all'interno di] col1 abbia lo stesso valore che hai appena inserito come quando effettui il calcolo.

Questo sarebbe sinonimo di ciò che sta accadendo con il bug, in cui NEWID viene creato per Col1 e quindi creato nuovamente per la colonna persistente:

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

Nel mio test, altre funzioni non deterministiche come RAND e valori di tempo non hanno prodotto lo stesso errore.

Per Martin, questo è stato portato a Microsoft ( https://connect.microsoft.com/SQLServer/Feedback/Details/2751288 ) dove ci sono commenti su questa pagina e l'analisi StackOverflow (sotto).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.