Porównanie dwóch zapytań w SQL Server 2012

14

Porównuję dwa zapytania w SQL Server 2012. Celem wyboru najlepszego zapytania jest wykorzystanie wszystkich istotnych informacji dostępnych w optymalizatorze zapytań. Oba zapytania dają takie same wyniki; maksymalne zamówienie dla wszystkich klientów.

Czyszczenie puli buforów zostało wykonane przed wykonaniem każdego zapytania za pomocą FREEPROCCACHE i DROPCLEANBUFFERS

Które zapytanie jest najlepszym wyborem na podstawie poniższych informacji?

-- Query 1 - return the maximum order id for a customer
SELECT orderid, custid
FROM Sales.Orders AS O1
WHERE orderid = (SELECT MAX(O2.orderid)
                 FROM Sales.Orders AS O2
                 WHERE O2.custid = O1.custid);


-- Query 2 - return the maximum order id for a customer
SELECT MAX(orderid), custid
FROM Sales.Orders AS O1
group by custid
order by custid

CZAS STATYSTYCZNY

Zapytanie 1 CZAS STATYSTYCZNY: Czas procesora = 0ms, czas, który upłynął = 24 ms

Zapytanie 2 CZAS STATYSTYCZNY: Czas procesora = 0 ms, czas, który upłynął = 23 ms

STATYSTYKA IO

Zapytanie 1 STATYSTYKI IO: Tabela „Zamówienia”. Liczba skanów 1, logiczne odczyty 5, fizyczne odczyty 2, wyprzedzające odczyty 0, logiczne odczyty 0, lob fizyczne odczyty 0, lob odczyty 0.

Zapytanie 2 STATYSTYKI IO: Tabela „Zamówienia”. Liczba skanów 1, logiczne odczyty 4, fizyczne odczyty 1, odczyt z wyprzedzeniem 8, logiczne odczyty 0, lob fizyczne odczyty 0, odczyt z wyprzedzeniem 0.

Plany wykonania

wprowadź opis zdjęcia tutaj

WYBIERZ właściwości Zapytanie 1

wprowadź opis zdjęcia tutaj

WYBIERZ właściwości Zapytanie 2

wprowadź opis zdjęcia tutaj

Wnioski:

Zapytanie 1

  1. Koszt partii 48%
  2. Odczyty logiczne 5
  3. Odczyty fizyczne 2
  4. Czytaj dalej Czyta: 0
  5. Czas procesora: 0ms
  6. Upływający czas 24ms
  7. Szacowany koszt poddrzewa: 0,0050276
  8. CompileCPU: 2
  9. CompileMemory: 384
  10. CompileTime: 2

Zapytanie 2

  1. Koszt partii 52%
  2. Odczyty logiczne 4
  3. Odczyty fizyczne 1
  4. Odczyt z wyprzedzeniem Odczyty: 8
  5. Czas procesora 0
  6. Upłynęło 23 ms
  7. Szacowany koszt poddrzewa: 0,0054782
  8. CompileCPU: 0
  9. CompileMemory: 192
  10. CompileTime: 0

Osobiście, mimo że Kwerenda 2 ma wyższy koszt partii zgodnie z planem graficznym, myślę, że jest bardziej skuteczna niż Kwerenda 1. To dlatego, że kwerenda 2 wymaga mniej logicznych odczytów, ma nieco krótszy czas, który upłynął, wartości compilecpu, kompilememory i kompilacji są niższy. odczyt z wyprzedzeniem wynosi 8 dla zapytania 2 i 0 dla zapytania 1.

Zaktualizuj 12:03

Definicja indeksu klastrowego

ALTER TABLE [Sales].[Orders] ADD  CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
(
    [orderid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Indeks nieklastrowany idx_nc_custid

CREATE NONCLUSTERED INDEX [idx_nc_custid] ON [Sales].[Orders]
(
    [custid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Craig Efrein
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Paul White 9

Odpowiedzi:

10

Uwielbiam twoje podejście do starannego rozważania strojenia zapytań oraz przeglądu opcji i planów. Chciałbym, żeby więcej programistów to zrobiło. Jedna uwaga byłaby - zawsze testuj z dużą ilością wierszy, patrząc na logiczne odczyty, jest to niewielki stół. Spróbuj wygenerować przykładowe obciążenie i ponownie uruchom zapytanie. Jeden mały problem - w zapytaniu głównym nie pytasz o zamówienie, w zapytaniu dolnym jesteś. Powinieneś porównać je i porównać z kolejnością.

Właśnie szybko utworzyłem tabelę SalesOrders z 200 000 zamówień sprzedaży - wciąż nie jest to duże wyzwanie. I uruchomiłem zapytania z ORDER BY w każdym. Trochę też grałem z indeksami.

Bez indeksu klastrowego na OrderID, tylko nieklastrowy indeks na CustID Drugie zapytanie wypadło lepiej. Zwłaszcza z zamówieniem zawartym w każdym. W pierwszym zapytaniu było dwa razy więcej odczytów niż w drugim, a procent kosztów wynosił 67% / 33% między zapytaniami.

Z indeksem klastrowanym na OrderID i indeksem nieklastrowanym tylko na CustID Działały one z podobną prędkością i dokładnie taką samą liczbą odczytów.

Sugeruję więc zwiększenie liczby wierszy i przeprowadzenie dalszych testów. Ale moja ostateczna analiza twoich zapytań -

Możesz zauważyć, że zachowują się bardziej podobnie, niż zdajesz sobie sprawę, gdy zwiększasz rzędy, więc miej to na uwadze i przetestuj w ten sposób.

Jeśli wszystko, co kiedykolwiek chcesz zwrócić, to maksymalny identyfikator zamówienia dla każdego klienta, a chcesz ustalić, że dzięki identyfikatorowi zamówienia, który jest największym identyfikatorem zamówienia, drugie zapytanie z tych dwóch jest najlepszym sposobem na przejście z mojego sposobu myślenia - to trochę prostsze i choć nieco droższe w oparciu o koszty poddrzewa, jest szybsze i łatwiejsze do rozszyfrowania. Jeśli pewnego dnia zamierzasz dodać inne kolumny do zestawu wyników? Następnie pierwsze zapytanie pozwala to zrobić.

Zaktualizowano: Jednym z twoich komentarzy pod twoim pytaniem było:

Należy pamiętać, że znalezienie najlepszego zapytania w tym pytaniu jest sposobem na udoskonalenie technik stosowanych do ich porównania.

Ale najlepsze na wynos do zrobienia tego - test z większą ilością danych - zawsze upewnia się, że masz dane spójne z produkcją i oczekiwaną przyszłą produkcją. Plany zapytań zaczynają szukać danych, gdy dodajesz więcej wierszy do tabel, i starasz się utrzymać dystrybucję, jakiej oczekujesz w produkcji. I zwracaj uwagę na takie rzeczy, jak włączanie lub wyłączanie Order By, tutaj nie sądzę, że robi to w końcu straszną różnicę, ale nadal warto się w to zagłębić.

Twoje podejście do porównywania tego poziomu szczegółowości i danych jest dobre. Koszty poddrzewa są w większości arbitralne i bez znaczenia, ale mimo to warto przynajmniej przyjrzeć się porównaniu między edycjami / zmianami, a nawet zapytaniami. Spojrzenie na statystyki czasu i IO są dość ważne, podobnie jak na planowanie wszystkiego, co wydaje się nie na miejscu ze względu na rozmiar danych, z którymi pracujesz i co próbujesz zrobić.

Mike Walsh
źródło
Witam ponownie, dziękuję za uwagi na temat korzystania z większych ilości danych. To nie pierwszy raz, kiedy ktoś to wychował. Ostatnim razem było jednak rozważenie możliwej fragmentacji z podziałów stron. Czy w Twojej próbie 200 000 wierszy sprawdziłeś fragmentację?
Craig Efrein
Cóż, w moim małym, szybkim 200-wierszowym przykładzie nie skupiałem się na fragmentacji, nie. Ale sposób, w jaki to zrobiłem, nie byłoby żadnego. Utworzyłem tabelę, wypełniłem ją, a następnie utworzyłem indeksy, więc były to indeksy świeżo utworzone. I to nie zmieni podejścia do przeglądania planów zapytań, które wydaje się być głównym pytaniem. Ilość danych jest duża - naprawdę duża - jeśli chodzi o dokładne spojrzenie na plany zapytań. Często widziałem przypadki, w których wyglądał świetnie w dev (z 1-10 wierszami) i był okropny w produkcji z prawdziwymi danymi. Ale twoje podejście jest dobre i mam nadzieję, że ta informacja i rozmowa w komentarzach pomaga
Mike Walsh
Skoro grupujemy według custid, w jaki sposób sprawiłeś, że wartości custid są wystarczająco losowe? Jedną z rzeczy, które pamiętam z moich odczytów, jest znaczenie odrębnych wartości. Gdyby klient miał tylko niewielką liczbę różnych klientów, wówczas koszt agregacji strumienia byłby nierealny.
Craig Efrein
Właśnie użyłem funkcji RAND, aby utworzyć 100 klientów i losowo przypisać jednego do każdego ID zamówienia. Przeprowadziłem szybką kontrolę. :)
Mike Walsh
Dzięki Mike za całą twoją pomoc. Ostatnie pytanie. Na ekranach SELECT właściwości z planu wykonania w 2012 r., Które podałem w moim pytaniu, na jakie wartości zwracasz uwagę?
Craig Efrein