Kolega MVP, Jamie Thomson, zwrócił niedawno uwagę na błąd „niewłaściwych wyników” w SQL Server, który może objawiać się, gdy spełnione są następujące warunki:
- Masz zindeksowany widok, który łączy co najmniej dwie tabele;
- te tabele są ograniczone w obu kierunkach przez jednokolumnowy klucz obcy;
- wykonujesz aktualizacje tabel podstawowych za pomocą
MERGE
który zawiera zarównoAKTUALIZACJA
i (USUŃ
lubWSTAW
) działania; i, - następnie wysyłasz zapytania, które odwołują się do indeksu w widoku (celowo lub nie).
Niestety, artykuł z bazy wiedzy opisujący problem (KB #2756471) zawiera dość mało szczegółów. Nie mówią ci, jak odtworzyć problem, ani nawet czego konkretnie powinieneś szukać, aby zobaczyć, czy dotyczy to ciebie; i nawet nie wspominają o MERGE
(co jest właściwie sednem problemu, a nie NOEXPAND
, a nie zwykłą aktualizację). Istnieje kilka dodatkowych szczegółów w elemencie Connect, które spowodowały poprawkę; miejmy nadzieję, że artykuł KB zostanie wkrótce zaktualizowany o więcej szczegółów.
W międzyczasie wynik, który możesz zobaczyć, to nieprawidłowe dane – lub lepiej, nieaktualne dane :zapytanie może pokazać starą wersję zaktualizowanych wierszy! Poświęciłem kilka minut na odtworzenie tego scenariusza w AdventureWorks, ale poniosłem sromotną porażkę. Na szczęście Paul White (blog | @SQL_Kiwi) napisał znakomity post opisujący scenariusz i pokazujący pełną kopię problemu.
Nie sądzę, abym mógł podkreślić, jak poważne to jest.
Z pewnością miliony klientów korzystają z widoków indeksowanych, wielu z nich przeniosło swój kod DML do MERGE
, a duża ich liczba jest w wersji Enterprise (lub nie, ale używa NOEXPAND
wskazówka lub bezpośrednio odwołują się do indeksu). Paul szybko zauważył, że NOEXPAND
nie jest wymagane do odtworzenia problemu w wersji Enterprise, a także odkrył wiele innych szczegółów wymaganych do odtworzenia błędu.
Ten post nie ma na celu kradzieży grzmotów z postów Jamiego lub Paula; to tylko próba ponownego wyrażenia obaw i podniesienia świadomości na ten temat. Jeśli masz zwyczaj ignorowania Aktualizacji zbiorczych, decydując się na czekanie na dodatki Service Pack i jest jakakolwiek szansa, że ten problem może Cię teraz dotyczyć, jesteś to winien sobie, nie wspominając o swoich interesariuszach i klientach, aby podjąć ten problem poważnie.
Więc co powinieneś zrobić?
Cóż, to, co zrobisz dalej, zależy od używanej wersji i edycji SQL Server oraz od tego, czy błąd faktycznie dotyczy Ciebie (lub może).
- Powinieneś zaktualizować do najnowszej aktualizacji zbiorczej dla swojego oddziału:
Oddział Naprawiono w CU Buduj Wymagana minimalna kompilacja
do zastosowania aktualizacjiArtykuł KB
(Pobierz)2008 Service Pack 3 CU #8 10.00.5828 10.00.5500 KB #2771833 2008 R2 Service Pack 1 CU #10 10.50.2868 10.50.2500 KB #2783135 2008 R2 Service Pack 2 CU #4 10.50.4270 10.000.4000 KB #2777358 2012 RTM CU #5 11.00.2395 11.000.2100 KB #2777772 2012 Service Pack 1 CU #2 11.00.3339 11.00.3000 KB #2790947 Tabela 1:Kompilacje zawierające poprawkę
- Jeśli nie zastosujesz poprawki, musisz przetestować wszystkie odwołania do swoich widoków, aby sprawdzić, czy zwracają one poprawne wyniki we wszystkich przypadkach — w tym po zaktualizowaniu tabel podstawowych za pomocą
MERGE . Jeśli tak się nie stanie (lub podejrzewasz, że może to mieć później wpływ), powinieneś odbudować indeks klastrowy we wszystkich widokach, których to dotyczy (lub naprawić zindeksowane widoki za pomocą
DBCC CHECKTABLE
, jak opisał Paul w swoim poście) i przestań używaćMERGE
w tych tabelach, dopóki nie zastosujesz poprawki. Jeśli nadal będziesz używaćMERGE
w stosunku do tabel podstawowych, przygotuj się do dalszej naprawy widoków, aby uniknąć problemu.
- Szybszym rozwiązaniem byłoby uniemożliwienie używania uszkodzonego indeksowanego widoku w ogóle, przy użyciu dowolnej z następujących wymaganych metod:
- zastosuj wskazówkę dotyczącą zapytania
OPCJA (ROZSZERZ WIDOKI)
do wszystkich odpowiednich zapytań; - usuń wszelkie wyraźne odniesienia do indeksu w widoku;
- w wersjach Standard lub innych, w których zindeksowane widoki nie są dopasowywane automatycznie, usuń wszystkie wystąpienia
NOEXPAND
.
Ale to oczywiście w dużej mierze pokonałoby cel indeksowanego widoku – równie dobrze może po prostu obniżyć indeks. To powiedziawszy, zwykle lepiej jest powoli uzyskiwać właściwe wyniki niż szybko uzyskiwać złe wyniki; więc może to w porządku.
- zastosuj wskazówkę dotyczącą zapytania
SQL Server 2008 z dodatkiem SP3
SQL Server 2008 R2 z dodatkiem SP1/SP2
SQL Server 2012 RTM/SP1
Twoje opcje, jeśli korzystasz z jednej z tych kompilacji:
SQL Server 2008 RTM/SP1/SP2
SQL Server 2008 R2 RTM
Niestety, korzystasz z wersji, która nie jest już objęta głównym wsparciem i jest mało prawdopodobne, że ten problem zostanie rozwiązany za Ciebie (chyba że korzystasz z rozszerzonego wsparcia i robisz dużo hałasu). Więc twoje opcje są tutaj ograniczone – albo przejdź do obsługiwanej gałęzi zgodnie z powyższą tabelą i zastosuj aktualizację zbiorczą lub wybierz jedną z innych opcji wspomnianych wcześniej.
SQL Server 2000
SQL Server 2005
Cóż, zła wiadomość jest taka, że korzystasz z wersji, która nie jest już obsługiwana. Dobrą wiadomością jest to, że w tym konkretnym przypadku nie ma to znaczenia – nie możesz użyć MERGE
w każdym razie, więc ten błąd nie może Cię dotyczyć.
Inne problemy z MERGE
Niestety jest to dalekie od pierwszego błędu, który widzieliśmy w MERGE
i prawdopodobnie nie będzie to ostatnie. Oto szybki wybór tuzinu MERGE
błędy, które są nadal oznaczone jako aktywne w Connect:
- #773895:MERGE nieprawidłowo zgłasza unikalne naruszenia kluczy
- #766165:MERGE ocenia filtrowany indeks na wiersz, a nie po operacji, co powoduje naruszenie filtrowanego indeksu
- #723696:Podstawowe upsert MERGE powodujące zakleszczenia
- #713699:Sprawdzanie asercji systemu nie powiodło się ("cxrowset.cpp":1528)
- #699055:plany zapytań MERGE umożliwiają naruszenia ograniczeń FK i CHECK
- #685800:Sparametryzowane DELETE i MERGE pozwalają na naruszenie ograniczeń klucza obcego
- #654746:scalanie w SQL2008 SP2 nadal cierpi z powodu „Próby ustawienia wartości kolumny nieobsługującej NULL na NULL”
- #635778:Części NOT MATCHED i MATCHED instrukcji SQL MERGE nie są zoptymalizowane
- #633132:Scalanie z filtrowanym źródłem nie działa prawidłowo
- #596086:Błąd instrukcji MERGE, gdy użyto INSERT/DELETE i przefiltrowano indeks
- #583719:instrukcja MERGE niepoprawnie traktuje kolumny obliczeniowe niepodlegające wartości null w niektórych scenariuszach
- #539084:MERGE Stmt:Warunek wyszukiwania w kolumnie bez klucza i ORDER BY w tabeli źródłowej przerywa MERGE całkowicie
Teraz może się zdarzyć, że niektóre z tych błędów zostały rzeczywiście naprawione, ale ich stan jest zły, ponieważ pętla z powrotem do Connect nie została zamknięta. Nawet jeśli tak jest, nie może to dotyczyć wszystkich z nich (i potencjalnie innych, których nie odkryłem).
Ponadto Dan Guzman zademonstrował, że MERGE
nie jest odporny na warunki rasowe i inne problemy ze współbieżnością. Rozwiązaniem jest użycie HOLDLOCK
(lub wyższy poziom izolacji); jednak powszechnym błędem jest to, że MERGE
jest całkowicie atomowa i w ogóle nie jest podatna na ten problem. Dlatego będę się głośno zastanawiać:ile MERGE
dostępne tam instrukcje obejmują HOLDLOCK
(lub są wykonywane pod SERIALIZABLE
)? Ile zostało dokładnie przetestowanych pod kątem problemów związanych ze współbieżnością?
Wniosek
Osobiście uważam, że składnia jest świetna (choć zniechęcająca do nauki), ale za każdym razem, gdy pojawia się problem, podważa to moje zaufanie do praktyczności zastąpienia istniejącego DML nową konstrukcją.
Mając to na uwadze, nie chcę być Kurczakiem Małym, ale nie czułbym się komfortowo polecając komukolwiek użycie MERGE
chyba że wdrożą niezwykle wszechstronne testy. Niektóre z tych problemów występują również w standardowym UPSERT
metodologii, ale tam problemy są bardziej oczywiste. MERGE
, tylko dzięki swojej jednoznacznej naturze sprawia, że chcesz uwierzyć w magię. Może kiedyś to się uda, ale teraz wiem, że bez poważnej pomocy nie da się przeciąć człowieka na pół.