DBCC CHECKDB nieusuwalne uszkodzenie: Widok indeksowany zawiera wiersze, które nie zostały utworzone przez definicję widoku

14

TL; DR: Mam nieusuwalne uszkodzenie w widoku indeksowanym. Oto szczegóły:


Bieganie

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

na jednej z moich baz danych powoduje następujący błąd:

Msg 8907, poziom 16, stan 1, wiersz 1 Indeks przestrzenny, indeks XML lub widok indeksowany „ViewName” (identyfikator obiektu 784109934) zawiera wiersze, które nie zostały utworzone przez definicję widoku. Nie musi to oznaczać problemu z integralnością danych w tej bazie danych. (...)

CHECKDB znalazł 0 błędów alokacji i 1 błędy spójności w tabeli „ViewName”.

repair_rebuild to minimalny poziom naprawy (...).

Rozumiem, że ten komunikat wskazuje, że zmaterializowane dane widoku indeksowanego „ViewName” nie są tożsame z tym, co generuje zapytanie bazowe. Jednak ręczna weryfikacja danych nie powoduje żadnych rozbieżności:

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)

NOEXPANDzostał użyty do wymuszenia użycia (tylko) indeksu na ViewName. FORCESCANzastosowano, aby zapobiec indeksowanemu dopasowaniu widoku. Plan wykonania potwierdza, że ​​oba środki działają.

Nie są zwracane żadne wiersze, co oznacza, że ​​dwie tabele są identyczne. (Są tylko kolumny całkowite i guid, zestawienia nie wchodzą w grę).

Błąd nie może zostać naprawiony poprzez odtworzenie indeksu w widoku lub uruchomienie DBCC CHECKDB REPAIR_ALLOW_DATA_LOSS. Powtarzanie poprawek również nie pomogło. Dlaczego DBCC CHECKDBzgłasza ten błąd? Jak się tego pozbyć?

(Nawet jeśli odbudowanie to naprawi, moje pytanie nadal będzie istnieć - dlaczego zgłaszany jest błąd, mimo że moje zapytania sprawdzania danych działają poprawnie?)


Aktualizacja: Błąd został naprawiony w niektórych wydaniach. Już nie mogę odtworzyć go w SQL Server 2014 SP2 CU 5. 2014 SP2 KB zawiera poprawkę bez artykule KB: Creating non-clustered index causes DBCC CheckDB With Extended_Logical_Checks to raise corruption error. Dwa błędy związane z tym związane zostały zamknięte:

usr
źródło
1
Czy mówisz, że upuściłeś i ponownie utworzyłeś indeks w widoku, a DBCC CHECKDB nadal zgłasza ten sam błąd? Co powiesz na upuszczenie widoku i utworzenie go od zera?
Aaron Bertrand
Z BOL: Rozwiązywanie problemów z błędami DBCC w widokach indeksowanych 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 Zredagowałem twój komentarz. [1]Notacja nie działa w komentarzu znak nogami.
Aaron Bertrand
Odtworzyłem wszystko. Pozwoliłem również na uruchomienie DBCC CHECKDB REPAIR_ALLOW_DATA_LOSS. Powiedział, że przebudował widok, ale następnie zgłosił ten sam błąd.
usr
Czy potrafisz pokazać definicję widoku (jeśli jest tu zbyt długa, to w ramce)?
Aaron Bertrand

Odpowiedzi:

14

Procesor zapytań może wygenerować niepoprawny plan wykonania dla (poprawnego) zapytania wygenerowanego przez DBCC, aby sprawdzić, czy indeks widoku generuje te same wiersze, co bazowe zapytanie widoku.

Plan wytwarzany przez procesor nieprawidłowo zapytania uchwyty NULLsdo ImageObjectIDkolumny. Niepoprawnie NULLspowoduje, że zapytanie widoku odrzuca tę kolumnę, jeśli tak nie jest. Sądząc, że NULLssą wykluczone, jest w stanie dopasować przefiltrowany indeks nieklastrowany w Userstabeli, w której są filtrowane ImageObjectID IS NOT NULL.

Tworząc plan, który korzysta z tego filtrowanego indeksu, zapewnia, że nie zostaną napotkane wiersze z NULLin ImageObjectID. Te wiersze są zwracane (poprawnie) z indeksu widoku, więc wydaje się, że istnieje uszkodzenie, gdy nie ma.

Definicja widoku to:

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

ONPorównanie równość między klauzula AdminUserIDi IDodrzuca NULLsw tych kolumnach, ale nie z ImageObjectIDkolumny.

Część zapytania wygenerowanego przez DBCC to:

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

Jest to ogólny kod, który porównuje wartości w sposób NULL-aware. Z pewnością jest to szczegółowe, ale logika jest w porządku.

Błąd w rozumowaniu procesora zapytań oznacza, że ​​może zostać wygenerowany plan zapytań, który niepoprawnie używa przefiltrowanego indeksu, jak w przykładzie fragmentu planu poniżej:

Błędny plan

Zapytanie DBCC ma inną ścieżkę kodu przez procesor zapytań niż zapytania użytkownika. Ta ścieżka do kodu zawiera błąd. Gdy generowany jest plan korzystający z filtrowanego indeksu, nie można go używać zUSE PLAN wskazówką, aby wymusić kształt tego planu za pomocą tego samego tekstu zapytania przesłanego z połączenia z bazą danych użytkownika.

Główna ścieżka kodu optymalizatora (dla zapytań użytkowników) nie zawiera tego błędu, więc jest specyficzna dla zapytań wewnętrznych, takich jak te generowane przez DBCC.

Paul White 9
źródło
Widzę wadliwy plan w zdarzeniu SQL Profiler Showplan XML. Oznaczę to jako odpowiedź .; Dlaczego DBCC buduje zapytanie w inny sposób niż zwykły procesor zapytań ?; Dodam link do tej odpowiedzi do elementu Connect.
usr
2
@usr DBCC wykonuje wszelkiego rodzaju rzeczy, które nie byłyby możliwe z połączenia użytkownika. Wyobrażam sobie, że działa w ten sposób, ponieważ musi, ale musiałbyś poprosić kogoś takiego jak Paul Randal, aby uzyskał prawdziwe szczegóły na ten temat. Oczywiście może nie być w stanie powiedzieć. Wiem, że istnieje wiele rzeczy poza DBCC, które robią jeszcze dziwniejsze rzeczy; niektórzy nawet konstruują plan wykonania, bez konieczności przechodzenia przez optymalizator!
Paul White 9
6

Dalsze dochodzenie pokazuje, że jest to błąd w DBCC CHECKDB. Błąd Microsoft Connect został otwarty: nieusuwalny błąd DBCC CHECKDB (który jest również fałszywie dodatni i poza tym dziwny) . Na szczęście udało mi się stworzyć repro, aby błąd można było znaleźć i naprawić.

Błąd można ukryć, grając ze schematem bazy danych. Usunięcie niepowiązanego przefiltrowanego indeksu lub usunięcie filtra ukrywa błąd. Aby uzyskać szczegółowe informacje, zobacz element Connect.

Element Connect zawiera również wewnętrzne zapytanie, którego DBCC CHECKDB używa do sprawdzania poprawności zawartości widoku. Nie zwraca żadnych wyników, co wskazuje, że jest to błąd.

Błąd został naprawiony w niektórych wydaniach. Nie mogę już go odtworzyć w SQL Server 2014 SP2 CU 5.

usr
źródło
Potrzebne było wiele danych (produkcyjnych) do odtworzenia błędu (co jest kolejnym dowodem na to, że przyczyną może być zmiana planu). Nie czuję się komfortowo, uwalniając dane, chociaż mogłem usunąć wszystkie kolumny oprócz dwóch z każdej tabeli. Problem, do którego masz link, powoduje uszkodzenie widoku. Odtworzyłem ten widok, aby nie powodować uszkodzeń spowodowanych przez DML; Czy zdajesz sobie sprawę z czegoś, co mogłoby spowodować inny plan, jeśli zapytanie jest uruchamiane w DBCC CHECKDB zamiast w normalnym oknie zapytania?
usr
Właśnie przesłano anonimową bazę danych. Oto skrypt, który odbudowuje wszystkie indeksy i odtwarza widok: pastebin.com/jPEALeEw (przydatne, aby zresetować wszystko i upewnić się, że struktura fizyczna jest w porządku). Inne pomocne skrypty: pastebin.com/KxNSwm2J Skrypty powinny po prostu zostać uruchomione, a problem powinien zostać natychmiast naprawiony.
usr
Mirror of .bak: mega.co.nz/…
usr
W dniu 11.0.3349 z -T272,4199,3604. 4199 włączonych poprawek procesora zapytań. Właśnie usunąłem ten TF .; Być może musimy wprowadzić odpowiedni plan zapytań. Teraz ustawiłem 1 GB pamięci RAM i zrestartowałem instancję (było 8 GB). To zmieniło jedno z dwóch połączeń scalających, które widziałem dla NLJ. Wciąż repros .; Aby wypróbować niektóre warianty planu, dodałem i usunąłem wiersze: pastebin.com/y972Sx4d Błąd wydaje się wyzwalać, jeśli otrzymuję połączenie przez scalenie lub skrót w części „lewy anty semi-złącz” w zapytaniu. Spróbuj tego: dodaj 100 000 wierszy do użytkowników. To niezawodnie daje dla mnie (równoległe) łączenie skrótowe.
usr
Właśnie przesłałem „plan.zip” do elementu połączenia, który zawiera różne plany wykonania dla zapytania DBCC CHECKDB. Przy różnej liczbie wierszy na uniwersytetach mogę opracować co najmniej trzy różne plany. Problem nie występuje tylko z planem połączeń pętli. Po połączeniu i scaleniu błąd jest powtarzalny.
usr