DBCC CHECKDB corruption non corrigible: la vue indexée contient des lignes qui n'ont pas été produites par la définition de la vue

14

TL; DR: J'ai une corruption non réparable dans une vue indexée. Voici les détails:


Fonctionnement

DBCC CHECKDB([DbName]) WITH EXTENDED_LOGICAL_CHECKS, DATA_PURITY, NO_INFOMSGS, ALL_ERRORMSGS

sur l'une de mes bases de données produit l'erreur suivante:

Msg 8907, niveau 16, état 1, ligne 1 L'index spatial, l'index XML ou la vue indexée 'ViewName' (ID d'objet 784109934) contient des lignes qui n'ont pas été produites par la définition de la vue. Cela ne représente pas nécessairement un problème d'intégrité avec les données de cette base de données. (...)

CHECKDB a trouvé 0 erreur d'allocation et 1 erreur de cohérence dans la table 'ViewName'.

repair_rebuild est le niveau de réparation minimum (...).

Je comprends que ce message indique que les données matérialisées de la vue indexée 'ViewName' ne sont pas identiques à ce que la requête sous-jacente produit. Cependant, la vérification manuelle des données ne révèle aucune anomalie:

SELECT * FROM ViewName WITH (NOEXPAND)
EXCEPT
SELECT ...
from T1 WITH (FORCESCAN)
join T2 on ...

SELECT ...
from T1 WITH (FORCESCAN)
join T2 on ...
EXCEPT
SELECT * FROM ViewName WITH (NOEXPAND)

NOEXPANDa été utilisé pour forcer l'utilisation de l'index (uniquement) sur ViewName. FORCESCANa été utilisé pour empêcher la correspondance des vues indexées. Le plan d'exécution confirme que les deux mesures fonctionnent.

Aucune ligne n'est renvoyée ici, ce qui signifie que les deux tables sont identiques. (Il n'y a que des colonnes entières et guid, les classements n'entrent pas en jeu).

L'erreur ne peut pas être corrigée en recréant l'index sur la vue ou en exécutant DBCC CHECKDB REPAIR_ALLOW_DATA_LOSS. La répétition des correctifs n'a pas aidé non plus. Pourquoi DBCC CHECKDBsignale cette erreur? Comment s'en débarrasser?

(Même si la reconstruction le corrigeait, ma question resterait posée - pourquoi une erreur est-elle signalée alors que mes requêtes de vérification des données ont réussi?)


Mise à jour: le bogue a été corrigé dans certaines versions. Je ne peux plus se reproduire dans SQL Server 2014 SP2 CU 5. 2014 SP2 KB contient un correctif sans KB article: Creating non-clustered index causes DBCC CheckDB With Extended_Logical_Checks to raise corruption error. Les deux bogues de connexion à ce sujet ont été fermés:

usr
la source
1
Voulez-vous dire que vous avez supprimé et recréé l'index sur la vue et que DBCC CHECKDB signale toujours la même erreur? Qu'en est-il de supprimer la vue et de la créer à partir de zéro?
Aaron Bertrand
De BOL: Dépannage des erreurs DBCC sur les vues indexées If the indexed view does not contain an aggregate over values of type float or real and you receive errors 8907 or 8708, drop the index on the view and re-create it. Do not use ALTER INDEX REBUILD to try to remove the differences between the stored and the computed view, because ALTER INDEX REBUILD does not recalculate the view before rebuilding the index. Then run DBCC CHECKTABLE on the View to verify no differences remain.
Kin Shah
@Kin, j'ai modifié votre commentaire. La [1]notation ne fonctionne pas dans la marque de commentaire.
Aaron Bertrand
J'ai tout recréé. J'ai également laissé DBCC CHECKDB REPAIR_ALLOW_DATA_LOSS s'exécuter. Il a déclaré avoir reconstruit la vue, mais il a ensuite signalé la même erreur.
usr
Pouvez-vous montrer la définition de la vue (si elle est trop longue ici, puis dans une boîte à pâte)?
Aaron Bertrand

Réponses:

14

Le processeur de requêtes peut produire un plan d'exécution non valide pour la requête (correcte) générée par DBCC pour vérifier que l'index de vue produit les mêmes lignes que la requête de vue sous-jacente.

Le plan produit par le processeur de requêtes ne gère pas correctement NULLsla ImageObjectIDcolonne. Il raisonne à tort que la requête de vue rejette NULLspour cette colonne, alors que ce n'est pas le cas. En pensant qu'ils NULLssont exclus, il est capable de faire correspondre l'index non cluster filtré sur la Userstable qui filtre ImageObjectID IS NOT NULL.

En produisant un plan qui utilise cet index filtré, il garantit que les lignes avec NULLin ImageObjectIDne sont pas rencontrées. Ces lignes sont retournées (correctement) à partir de l'index de vue, il semble donc qu'il y ait une corruption quand il n'y en a pas.

La définition de la vue est:

SELECT
    dbo.Universities.ID AS Universities_ID, 
    dbo.Users.ImageObjectID AS Users_ImageObjectID
FROM dbo.Universities
JOIN dbo.Users
    ON dbo.Universities.AdminUserID = dbo.Users.ID

La ONcomparaison d'égalité de clause entre AdminUserIDet IDrejette NULLsdans ces colonnes, mais pas à partir de la ImageObjectIDcolonne.

Une partie de la requête générée par DBCC est:

SELECT [Universities_ID], [Users_ImageObjectID], 0 as 'SOURCE'
FROM [dbo].[mv_Universities_Users_ID] tOuter WITH (NOEXPAND) 
WHERE NOT EXISTS
( 
    SELECT 1 
    FROM   [dbo].[mv_Universities_Users_ID] tInner
    WHERE 
    (
        (
            (
                [tInner].[Universities_ID] = [tOuter].[Universities_ID]
            ) 
            OR 
            (
                [tInner].[Universities_ID] IS NULL
                AND [tOuter].[Universities_ID] IS NULL
            )
        )
        AND
        (
            (
                [tInner].[Users_ImageObjectID] = [tOuter].[Users_ImageObjectID]
            ) 
            OR 
            (
                [tInner].[Users_ImageObjectID] IS NULL 
                AND [tOuter].[Users_ImageObjectID] IS NULL
            )
        )
    )
)
OPTION (EXPAND VIEWS);

Il s'agit d'un code générique qui compare les valeurs de manière NULLconsciente. C'est certainement verbeux, mais la logique est bonne.

Le bogue dans le raisonnement du processeur de requête signifie qu'un plan de requête qui utilise incorrectement l'index filtré peut être produit, comme dans l'exemple de fragment de plan ci-dessous:

Plan erroné

La requête DBCC prend un chemin de code différent via le processeur de requêtes à partir des requêtes utilisateur. Ce chemin de code contient le bogue. Lorsqu'un plan utilisant l'index filtré est généré, il ne peut pas être utilisé avec l' USE PLANindice pour forcer cette forme de plan avec le même texte de requête soumis à partir d'une connexion à la base de données utilisateur.

Le chemin de code principal de l'optimiseur (pour les requêtes des utilisateurs) ne contient pas ce bogue, il est donc spécifique aux requêtes internes comme celles générées par DBCC.

Paul White 9
la source
Je peux voir le plan défectueux dans l'événement XML Showplan SQL Profiler. Je vais marquer cela comme la réponse .; Pourquoi DBCC construit-il la requête d'une manière différente de celle du processeur de requêtes normal ?; Je vais ajouter un lien vers cette réponse à l'élément de connexion.
usr
2
@usr DBCC fait toutes sortes de choses qui ne seraient pas possibles à partir d'une connexion utilisateur. J'imagine que cela fonctionne de cette façon parce qu'il le faut, mais il faudrait demander à quelqu'un comme Paul Randal d'obtenir les vrais détails à ce sujet. Il n'est peut-être pas libre de le dire, bien sûr. Je sais qu'il y a beaucoup de choses en dehors de DBCC qui font des choses encore plus étranges; certains construisent même un plan d'exécution sans passer par l'optimiseur du tout!
Paul White 9
6

Une enquête plus approfondie montre qu'il s'agit d'un bogue dans DBCC CHECKDB. Un bogue Microsoft Connect a été ouvert: erreur DBCC CHECKDB non réparable (qui est également un faux positif et autrement étrange) . Heureusement, j'ai pu produire une repro afin que le bogue puisse être trouvé et corrigé.

Le bogue peut être masqué en jouant avec le schéma de base de données. La suppression d'un index filtré non lié ou la suppression du filtre masque le bogue. Pour plus de détails, veuillez consulter l'élément de connexion.

L'élément de connexion contient également la requête interne que DBCC CHECKDB utilise pour valider le contenu de la vue. Il ne renvoie aucun résultat, montrant qu'il s'agit d'un bogue.

Le bug a été corrigé dans certaines versions. Je ne peux plus le reproduire dans SQL Server 2014 SP2 CU 5.

usr
la source
Un grand nombre de données (de production) étaient nécessaires pour reproduire le bogue (ce qui est une preuve supplémentaire qu'un changement de plan pourrait être la cause). Je ne suis pas à l'aise de publier les données même si j'ai pu supprimer toutes les colonnes sauf deux de chaque table. Le problème que vous avez lié nécessite une corruption de la vue. J'ai recréé la vue afin qu'aucune corruption due à DML ne puisse en être la cause .; Êtes-vous au courant de quelque chose qui pourrait entraîner un plan différent si la requête est exécutée sous DBCC CHECKDB plutôt que dans une fenêtre de requête normale?
usr
Une base de données anonymisée vient d'être téléchargée. Voici un script qui reconstruit tous les index et recrée la vue: pastebin.com/jPEALeEw (utile pour tout réinitialiser et s'assurer que la structure physique est correcte). Autres scripts utiles: pastebin.com/KxNSwm2J Les scripts doivent simplement être exécutés et le problème doit être reproduit immédiatement.
usr
Miroir du .bak: mega.co.nz/…
usr
Le 11.0.3349 avec -T272,4199,3604. 4199 correctifs du processeur de requêtes activés. Je viens de supprimer ce TF .; Peut-être que nous devons induire le bon plan de requête. J'ai maintenant mis 1 Go de RAM et redémarré l'instance (au lieu de 8 Go). Cela a changé l'une des deux jointures de fusion que je voyais pour NLJ. Encore des repro .; Pour essayer quelques variantes de plan, j'ai ajouté et supprimé des lignes: pastebin.com/y972Sx4d Le bogue semble se déclencher si je reçois une jointure de fusion ou un hachage dans la partie "gauche anti semi-jointure" de la requête. Essayez ceci: ajoutez 100 000 lignes aux utilisateurs. Cela donne de manière fiable une jointure de hachage (parallèle) pour moi.
usr
Je viens de télécharger "plans.zip" sur l'élément de connexion qui contient différents plans d'exécution pour la requête DBCC CHECKDB. Avec différents nombres de lignes dans les universités, je peux produire au moins trois plans différents. Ce n'est qu'avec le plan de jointures en boucle que le problème ne se produit pas. Avec la fusion et le hachage, le bogue est reproductible.
usr