Czy SQL Server może oceniać A <> B
jako A < B OR A > B
, nawet jeśli jedno z wyrażeń jest niedeterministyczne?
Jest to nieco kontrowersyjny punkt, a odpowiedź brzmi „tak”.
Najlepsza znana mi dyskusja została udzielona w odpowiedzi na raport o błędzie Connect Itzika Ben-Gana Błąd z NEWID i wyrażeniami tabelowymi , który został zamknięty, ponieważ nie zostanie naprawiony. Connect został wycofany, więc znajduje się tam link do archiwum internetowego. Niestety, wiele przydatnych materiałów zostało utraconych (lub utrudnionych do znalezienia) przez upadek Connect. W każdym razie najbardziej przydatne cytaty z Jim Hogg z Microsoft są:
To uderza w sedno problemu - czy optymalizacja może zmienić semantykę programu? Tj .: jeśli program daje pewne odpowiedzi, ale działa wolno, czy jest to uzasadnione, aby Optymalizator zapytań przyspieszył działanie tego programu, a jednocześnie zmienił podane wyniki?
Zanim krzykniesz „NIE!” (moja osobista skłonność też :-), zastanów się: dobrą wiadomością jest to, że w 99% przypadków odpowiedzi są takie same. Optymalizacja zapytań jest więc wyraźną wygraną. Zła wiadomość jest taka, że jeśli zapytanie zawiera kod wywołujący skutki uboczne, wówczas różne plany MOGĄ rzeczywiście dać różne wyniki. NEWID () to jedna z takich efektów ubocznych (niedeterministyczna), która ujawnia różnicę. [W rzeczywistości, jeśli eksperymentujesz, możesz opracować inne - na przykład, ocenę zwarcia klauzul AND: spraw, aby druga klauzula generowała arytmetyczny podział przez zero - różne optymalizacje mogą wykonać tę drugą klauzulę PRZED pierwszą klauzulą] To odzwierciedla Wyjaśnienie Craiga, gdzie indziej w tym wątku, że SqlServer nie gwarantuje wykonania operatorów skalarnych.
Mamy więc wybór: jeśli chcemy zagwarantować pewne zachowanie w obecności niedeterministycznego (powodującego skutki uboczne) kodu - tak aby wyniki JOIN na przykład podążały za semantyką wykonania zagnieżdżonej pętli - wtedy może użyć odpowiednich OPCJI, aby wymusić takie zachowanie - jak wskazuje UC. Ale wynikowy kod będzie działał wolno - taki jest koszt spowolnienia Optymalizatora zapytań.
To powiedziawszy, przesuwamy Optymalizator zapytań w kierunku zachowania „zgodnie z oczekiwaniami” dla NEWID () - zmniejszając wydajność dla „wyników zgodnie z oczekiwaniami”.
Jednym z przykładów zmiany zachowania w tym zakresie w czasie jest to, że NULLIF działa niepoprawnie z niedeterministycznymi funkcjami, takimi jak RAND () . Istnieją również inne podobne przypadki, w których stosuje się np. COALESCE
Podzapytanie, które może dawać nieoczekiwane wyniki i które są również rozwiązywane stopniowo.
Jim kontynuuje:
Zamykanie pętli. . . Omówiłem to pytanie z zespołem deweloperów. I ostatecznie postanowiliśmy nie zmieniać obecnego zachowania z następujących powodów:
1) Optymalizator nie gwarantuje synchronizacji ani liczby realizacji funkcji skalarnych. To długo uznana zasada. Jest to podstawowa „swoboda”, która pozwala optymalizatorowi wystarczająco dużo swobody, aby uzyskać znaczną poprawę w realizacji planu zapytań.
2) To „zachowanie w jednym rzędzie” nie jest nowym zagadnieniem, chociaż nie jest szeroko omawiane. Zaczęliśmy poprawiać jego zachowanie z powrotem w wydaniu Yukon. Ale bardzo trudno jest dokładnie określić, we wszystkich przypadkach, dokładnie to, co to znaczy! Na przykład, czy dotyczy to wierszy tymczasowych obliczanych „w drodze” do wyniku końcowego? - w takim przypadku zależy to wyraźnie od wybranego planu. Czy może dotyczy to tylko wierszy, które ostatecznie pojawią się w ukończonym wyniku? - dzieje się tutaj paskudna rekurencja, jestem pewien, że się zgodzisz!
3) Jak wspomniałem wcześniej, domyślnie „optymalizujemy wydajność” - co jest dobre w 99% przypadków. 1% przypadków, w których może to zmienić wyniki, jest dość łatwy do wykrycia - powodujące skutki uboczne „funkcje”, takie jak NEWID, i łatwe do „naprawienia” (w konsekwencji handel perf). Ta domyślna „optymalizacja wydajności” ponownie ma długą tradycję i jest akceptowana. (Tak, nie jest to postawa wybierana przez kompilatory dla konwencjonalnych języków programowania, ale niech tak będzie).
Nasze rekomendacje to:
a) Unikaj polegania na nie gwarantowanym semantyce czasowej i liczbie wykonań. b) Unikaj używania NEWID () głęboko w wyrażeniach tabelowych. c) Użyj OPCJI, aby wymusić określone zachowanie (handel perf)
Mam nadzieję, że to wyjaśnienie pomoże wyjaśnić nasze przyczyny zamknięcia tego błędu, ponieważ „nie da się naprawić”.
Co ciekawe, AND NOT (s_guid = NEWID())
daje ten sam plan wykonania
Jest to konsekwencja normalizacji, która dzieje się bardzo wcześnie podczas kompilacji zapytań. Oba wyrażenia kompilują się dokładnie w tej samej znormalizowanej formie, więc tworzony jest ten sam plan wykonania.