Wyrażenie zapytania przy użyciu innej składni może czasem pomóc w wyrażeniu chęci użycia indeksu nieklastrowanego do optymalizatora. Poniższy formularz powinien zawierać plan, który chcesz:
SELECT
[ID],
[DeviceID],
[IsPUp],
[IsWebUp],
[IsPingUp],
[DateEntered]
FROM [dbo].[Heartbeats]
WHERE
[ID] IN
(
-- Keys
SELECT TOP (1000)
[ID]
FROM [dbo].[Heartbeats]
WHERE
[DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
AND [DateEntered] < CONVERT(datetime, '2011-08-31', 121)
);
Porównaj ten plan z planem utworzonym, gdy indeks nieklastrowany jest wymuszony z podpowiedź:
SELECT TOP (1000)
*
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE
[DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
Plany są zasadniczo takie same (wyszukiwanie klucza jest niczym innym jak wyszukiwaniem indeksu klastrowego). Obie formy planu wykonają tylko jedno wyszukiwanie indeksu klastrowanego i maksymalnie 1000 wyszukiwań indeksu klastrowego.
Ważną różnicą jest pozycja najlepszego operatora. Umieszczony między dwoma poszukiwaniami, Top zapobiega zamianie przez optymalizator dwóch operacji wyszukiwania na logicznie równoważny skan indeksu klastrowanego. Optymalizator działa poprzez zastąpienie części planu logicznego równoważnymi operacjami relacyjnymi. Góra nie jest operatorem relacyjnym, więc przepisywanie zapobiega transformacji do skanowania indeksu klastrowego. Gdyby optymalizator był w stanie zmienić pozycję operatora Top, nadal wolałby skanowanie niż wyszukiwanie + wyszukiwanie ze względu na sposób szacowania kosztów.
Kalkulacja kosztów skanów i poszukiwań
Na bardzo wysokim poziomie model kosztów optymalizatora dla skanów i prób jest dość prosty: szacuje się, że 320 losowych prób kosztuje tyle samo, co odczyt 1350 stron na skanie. Prawdopodobnie jest to mało podobne do możliwości sprzętowych jakiegokolwiek konkretnego nowoczesnego systemu I / O, ale działa całkiem dobrze jako praktyczny model.
Model wprowadza również szereg uproszczeń, z których najważniejszym jest to, że każde zapytanie rozpoczyna się bez danych lub stron indeksowych znajdujących się już w pamięci podręcznej. Implikacja jest taka, że każde we / wy spowoduje fizyczne we / wy - chociaż rzadko tak jest w praktyce. Nawet przy zimnej pamięci podręcznej, pobieranie z wyprzedzeniem i wyprzedzanie odczytu oznacza, że potrzebne strony są w rzeczywistości prawdopodobnie w pamięci, zanim procesor zapytań ich potrzebuje.
Inną kwestią jest to, że pierwsze żądanie wiersza, którego nie ma w pamięci, spowoduje pobranie całej strony z dysku. Kolejne żądania wierszy na tej samej stronie najprawdopodobniej nie spowodują fizycznego We / Wy. Model wyceny zawiera logikę uwzględniającą niektóre z takich efektów, ale nie jest idealny.
Wszystkie te rzeczy (i więcej) oznaczają, że optymalizator zwykle przełącza się na skanowanie wcześniej, niż powinno. Losowe operacje we / wy są „znacznie droższe” niż „sekwencyjne” operacje we / wy, jeśli wynikiem operacji fizycznej jest uzyskanie szybkiego dostępu do stron w pamięci. Nawet tam, gdzie wymagany jest fizyczny odczyt, skanowanie może w ogóle nie skutkować odczytami sekwencyjnymi z powodu fragmentacji, a wyszukiwania mogą być kolokowane w taki sposób, że wzorzec jest zasadniczo sekwencyjny. Dodajmy do tego zmieniającą się charakterystykę wydajności współczesnych systemów I / O (szczególnie półprzewodnikowych), a całość zaczyna wyglądać bardzo niepewnie.
Cele rzędu
Obecność operatora Top w planie modyfikuje podejście do kalkulacji kosztów. Optymalizator jest wystarczająco inteligentny, aby wiedzieć, że znalezienie 1000 wierszy za pomocą skanowania prawdopodobnie nie będzie wymagało skanowania całego indeksu klastrowego - może zatrzymać się, gdy tylko 1000 wierszy zostanie znalezionych. Określa „cel rzędu” wynoszący 1000 wierszy u górnego operatora i korzysta z informacji statystycznych, aby stamtąd wrócić, aby oszacować, ile wierszy oczekuje od źródła wiersza (w tym przypadku skan). O szczegółach tego obliczenia pisałem tutaj .
Obrazy w tej odpowiedzi zostały utworzone za pomocą SQL Sentry Plan Explorer .