Est-il légal pour SQL Server de remplir des colonnes PERSISTED avec des données qui ne correspondent pas à la définition?

16

Je poursuis cette question sur les valeurs étranges dans une PERSISTEDcolonne calculée. La réponse ici fait quelques suppositions sur la façon dont ce comportement est devenu.

Je pose la question suivante: n'est-ce pas un bug pur et simple? Les PERSISTEDcolonnes peuvent-elles toujours se comporter de cette façon?

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

Notez que les données apparaissent "impossibles" car les valeurs de la colonne calculée ne correspondent pas à sa définition.

Il est bien connu que les fonctions non déterministes dans les requêtes peuvent se comporter étrangement, mais ici cela semble violer le contrat des colonnes calculées persistantes et, par conséquent, devrait être illégal.

L'insertion de nombres aléatoires pourrait être un scénario artificiel, mais que se passerait-il si nous insérions des NEWID()valeurs ou SYSUTCDATETIME()? Je pense que c'est une question pertinente qui pourrait pratiquement se manifester.

usr
la source

Réponses:

9

C'est certainement un bug. Le fait que les col1valeurs se soient avérées être le résultat d'une expression impliquant des nombres aléatoires ne change clairement pas ce que la valeur correcte col2est censée être. DBCC CHECKDBrenvoie une erreur si celle-ci est exécutée sur une table 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

Donne (pour mon test qui comportait une ligne "impossible")

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

Il indique également que

repair_allow_data_loss est le niveau de réparation minimum pour les erreurs trouvées par DBCC CHECKDB

Et si l'option de réparation est utilisée, la ligne entière est supprimée sans cérémonie car elle n'a aucun moyen de savoir quelle colonne est corrompue.

Attacher un débogueur montre que le NEWID()est évalué deux fois par ligne insérée. Une fois avant l' CASEexpression est évaluée et une fois à l'intérieur.

entrez la description de l'image ici

Une solution de contournement possible pourrait être d'utiliser

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

Ce qui, pour une raison ou une autre, évite le problème et n'évalue l'expression qu'une fois par ligne.

Martin Smith
la source
2

D'après la conversation de commentaires, le consensus semble être que la réponse à la question du PO est que cela constitue un bug (c'est-à-dire qu'il devrait être illégal).

L'OP fait référence à l'analyse de Vladimir Baranov sur StackOverflow, où ils déclarent:

"Première fois pour Col1, deuxième fois pour l'instruction CASE de la colonne persistante.

L'optimiseur ne sait pas, ou ne se soucie pas dans ce cas que NEWID est une fonction non déterministe et l'appelle deux fois. "

Autrement dit, il faut s'attendre à ce que [le NEWID () dans] col1 ait la même valeur que vous venez d'insérer que lorsque vous effectuez le calcul.

Ce serait synonyme de ce qui se passe avec le bogue, où NEWID est créé pour Col1, puis recréé pour la colonne persistante:

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

Lors de mes tests, d'autres fonctions non déterministes comme RAND et les valeurs de temps n'ont pas entraîné le même bogue.

Par Martin, cela a été signalé à Microsoft ( https://connect.microsoft.com/SQLServer/Feedback/Details/2751288 ) où il y a des commentaires sur cette page et l'analyse StackOverflow (ci-dessous).

John
la source