Jest to część serii Problematic Operators programu SQL Server Internals. Aby przeczytać pierwszy post, kliknij tutaj.
SQL Server istnieje od ponad 30 lat, a ja pracuję z SQL Server prawie tak długo. Widziałem wiele zmian na przestrzeni lat (i dziesięcioleci!) i wersji tego niesamowitego produktu. W tych postach podzielę się z Wami tym, jak patrzę na niektóre funkcje lub aspekty SQL Server, czasami wraz z odrobiną perspektywy historycznej.
Ostatnio mówiłem o operacji skanowania w planie kwerend SQL Server jako potencjalnie problematycznym operatorze w diagnostyce SQL Server. Chociaż skanowanie jest często używane tylko dlatego, że nie ma użytecznego indeksu, zdarzają się sytuacje, w których skanowanie jest w rzeczywistości lepszym wyborem niż operacja wyszukiwania indeksu.
W tym artykule opowiem Ci o innej rodzinie operatorów, która czasami jest postrzegana jako problematyczna:haszowaniu. Haszowanie to bardzo dobrze znany algorytm przetwarzania danych, który istnieje od wielu dziesięcioleci. Studiowałem to na moich zajęciach ze struktur danych, kiedy po raz pierwszy studiowałem informatykę na uniwersytecie. Jeśli chcesz uzyskać podstawowe informacje na temat funkcji haszowania i funkcji haszowania, możesz zapoznać się z tym artykułem w Wikipedii. Jednak SQL Server nie dodał haszowania do swojego repertuaru opcji przetwarzania zapytań aż do SQL Server 7. (Na marginesie wspomnę, że SQL Server używał haszowania w niektórych swoich wewnętrznych algorytmach wyszukiwania. Jak wspomniano w artykule Wikipedii , haszowanie wykorzystuje specjalną funkcję do mapowania danych o dowolnym rozmiarze na dane o stałym rozmiarze. SQL używał mieszania jako techniki wyszukiwania do mapowania każdej strony z bazy danych o dowolnym rozmiarze do bufora w pamięci, który ma stały rozmiar. , kiedyś istniała opcja sp_configure zwane „zasobami mieszającymi”, które pozwalają kontrolować liczbę zasobników używanych do mieszania stron bazy danych do buforów pamięci).
Co to jest haszowanie?
Hashowanie to technika wyszukiwania, która nie wymaga porządkowania danych. SQL Server może go używać do operacji JOIN, operacji agregacji (DISTINCT lub GROUP BY) lub operacji UNION. Cechą wspólną tych trzech operacji jest to, że podczas wykonywania aparat zapytań szuka pasujących wartości. W JOIN chcemy znaleźć wiersze w jednej tabeli (lub zestawie wierszy), które mają pasujące wartości z wierszami w innej. (I tak, znam sprzężenia, które nie porównują wierszy w oparciu o równość, ale te sprzężenia nierównoległe są nieistotne dla tej dyskusji). i DISTINCT, szukamy pasujących wartości, aby je wykluczyć. (Tak, wiem, że UNION ALL jest wyjątkiem.)
Przed wersją SQL Server 7 jedynym sposobem, w jaki te operacje mogły łatwo znaleźć pasujące wartości, było sortowanie danych. Tak więc, jeśli nie istnieje indeks, który utrzymywałby dane w kolejności posortowania, plan zapytania doda do planu operację SORT. Hashing organizuje Twoje dane pod kątem wydajnego wyszukiwania, umieszczając wszystkie wiersze, które mają ten sam wynik z wewnętrznej funkcji mieszającej, w tym samym „wiadrze mieszającym”.
Aby uzyskać bardziej szczegółowe wyjaśnienie operacji mieszania skrótu SQL Server, w tym diagramy, zapoznaj się z tym wpisem w blogu z SQL Shack.
Gdy haszowanie stało się opcją, SQL Server nie pomijał całkowicie możliwości sortowania danych przed łączeniem lub agregacją, ale po prostu stał się możliwością do rozważenia przez optymalizatora. Ogólnie jednak, jeśli próbujesz połączyć, agregować lub wykonać UNION na nieposortowanych danych, optymalizator zwykle wybierze operację skrótu. Tak wiele osób zakłada, że HASH JOIN (lub inna operacja HASH) w planie oznacza, że nie masz odpowiednich indeksów i że powinieneś zbudować odpowiednie indeksy, aby uniknąć operacji haszowania.
Spójrzmy na przykład. Najpierw utworzę dwie niezindeksowane tabele.
USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;
GO
SELECT * INTO Details FROM Sales.SalesOrderDetail;
GO
DROP TABLE IF EXISTS Headers;
GO
SELECT * INTO Headers FROM Sales.SalesOrderHeader;
GO
Now, I’ll join these two tables together and filter the rows in the Details table:
SELECT *
FROM Details d JOIN Headers h
ON d.SalesOrderID = h.SalesOrderID
WHERE SalesOrderDetailID < 100;
Quest Spotlight Tuning Pack nie wskazuje na problem z haszowaniem. Podświetla tylko dwa skany tabeli.
Sugestie zalecają budowanie indeksu w każdej tabeli, który zawiera każdą pojedynczą kolumnę niekluczową jako kolumnę INCLUDED. Rzadko biorę te rekomendacje (o czym wspomniałem w poprzednim poście). Zbuduję tylko indeks na podstawie Szczegółów tabeli, w kolumnie łączenia i nie zawierają żadnych uwzględnionych kolumn.
CREATE INDEX Header_index on Headers(SalesOrderID)
;
Po zbudowaniu tego indeksu HASH JOIN znika. Indeks sortuje dane w Nagłówkach tabeli i umożliwia programowi SQL Server znalezienie pasujących wierszy w tabeli wewnętrznej przy użyciu kolejności sortowania indeksu. Teraz najdroższą częścią planu jest skanowanie na zewnętrznym stole (Szczegóły ), które można zmniejszyć, tworząc indeks na SalesOrderID kolumna w tej tabeli. Zostawię to jako ćwiczenie dla czytelnika.
Jednak plan z HASH JOIN nie zawsze jest złą rzeczą. Operatorem alternatywnym (z wyjątkiem szczególnych przypadków) jest JOIN ZAGNIEŻDŻONE PĘTLE i jest to zwykle wybór, gdy istnieją dobre indeksy. Jednak operacja pętli zagnieżdżonych wymaga wielokrotnych przeszukań tabeli wewnętrznej. Poniższy pseudokod przedstawia algorytm łączenia zagnieżdżonych pętli:
for each row R1 in the outer table
for each row R2 in the inner table
if R1 joins with R2
return (R1, R2)
Jak sama nazwa wskazuje, JOIN z zagnieżdżoną pętlą jest wykonywane jako pętla zagnieżdżona. Wyszukiwanie w tabeli wewnętrznej będzie zwykle przeprowadzane wiele razy, raz dla każdego kwalifikującego się wiersza w tabeli zewnętrznej. Nawet jeśli kwalifikuje się tylko kilka procent wierszy, jeśli tabela jest bardzo duża (być może w setkach milionów, miliardów lub wierszy), będzie wiele wierszy do przeczytania. W systemie, który jest powiązany z wejściami/wyjściami, te miliony lub miliardy odczytów mogą stanowić prawdziwe wąskie gardło.
Z drugiej strony HASH JOIN nie wykonuje wielokrotnych odczytów żadnej tabeli. Odczytuje raz tabelę zewnętrzną, aby utworzyć wiaderka z haszem, a następnie raz odczytuje tabelę wewnętrzną, sprawdzając wiadra z haszowaniem, aby zobaczyć, czy istnieje pasujący wiersz. Mamy górną granicę pojedynczego przejścia przez każdy stół. Tak, do obliczenia funkcji skrótu i zarządzania zawartością zasobników potrzebne są zasoby procesora. Do przechowywania zaszyfrowanych informacji potrzebne są zasoby pamięci. Ale jeśli masz system związany z We/Wy, możesz mieć wolne zasoby pamięci i procesora. HASH JOIN może być rozsądnym wyborem dla optymalizatora w sytuacjach, gdy Twoje zasoby we/wy są ograniczone i łączysz się z bardzo dużymi tabelami.
Oto pseudokod algorytmu łączenia haszującego:
for each row R1 in the build table
begin
calculate hash value on R1 join key(s)
insert R1 into the appropriate hash bucket
end
for each row R2 in the probe table
begin
calculate hash value on R2 join key(s)
for each row R1 in the corresponding hash bucket
if R1 joins with R2
output (R1, R2)
end
Jak wspomniano wcześniej, mieszanie może być również używane do operacji agregacji (a także UNION). Ponownie, jeśli istnieje przydatny indeks, w którym dane są już posortowane, grupowanie danych można przeprowadzić bardzo wydajnie. Jednak jest też wiele sytuacji, w których haszowanie wcale nie jest złym operatorem. Rozważ zapytanie podobne do poniższego, które grupuje dane w Szczegóły tabela (utworzona powyżej) przez ID produktu kolumna. W tabeli jest 121 317 wierszy i tylko 266 różnych ID produktu wartości.
SELECT ProductID, count(*)
FROM Details
GROUP BY ProductID;
GO
Korzystanie z operacji haszujących
Aby korzystać z haszowania, SQL Server musi tylko utworzyć i utrzymywać 266 zasobników, co nie jest dużo. W rzeczywistości Quest Spotlight Tuning Pack nie wskazuje na jakiekolwiek problemy z tym zapytaniem.
Tak, musi wykonać skanowanie tabeli, ale to dlatego, że musimy zbadać każdy wiersz w tabeli, a wiemy, że skanowanie nie zawsze jest czymś złym. Indeks pomógłby tylko we wstępnym sortowaniu danych, ale użycie agregacji skrótu dla tak małej liczby grup nadal zwykle zapewnia rozsądną wydajność, nawet bez dostępnego użytecznego indeksu.
Podobnie jak skanowanie tabel, operacje haszujące są często postrzegane jako „zły” operator w planie. Istnieją przypadki, w których można znacznie poprawić wydajność, dodając przydatne indeksy w celu usunięcia operacji skrótu, ale nie zawsze jest to prawdą. A jeśli próbujesz ograniczyć liczbę indeksów w tabelach, które są mocno aktualizowane, powinieneś mieć świadomość, że operacje haszujące nie zawsze są czymś, co trzeba „naprawić”, więc pozostawienie zapytania z użyciem skrótu może być rozsądną rzeczą do zrobienia. Ponadto w przypadku niektórych zapytań dotyczących dużych tabel działających w systemach powiązanych we/wy haszowanie może w rzeczywistości zapewnić lepszą wydajność niż alternatywne algorytmy ze względu na ograniczoną liczbę odczytów, które należy wykonać. Jedynym sposobem, aby wiedzieć na pewno, jest przetestowanie różnych możliwości w twoim systemie, z twoimi zapytaniami i danymi.
W poniższym poście z tej serii opowiem Ci o innych problematycznych operatorach, które mogą pojawić się w Twoich planach zapytań, więc sprawdź ponownie wkrótce!