W tym tygodniu wojna ogniowa na liście wydajności pgsql ponownie obraca się wokół faktu, że PostgreSQL nie ma tradycyjnej składni wskazówek dostępnej w innych bazach danych. Jest to mieszanka powodów technicznych i pragmatycznych:
- Wprowadzanie podpowiedzi jest częstym źródłem późniejszych problemów, ponieważ naprawienie miejsca zapytania raz w szczególnym przypadku nie jest dobrym podejściem. W miarę jak Twój zestaw danych rośnie i prawdopodobnie zmienia się również dystrybucja, pomysł, o którym wspomniałeś, gdy był mały, może stać się coraz bardziej złym pomysłem.
- Dodanie użytecznego interfejsu podpowiedzi skomplikowałoby kod optymalizatora, który w obecnej postaci jest wystarczająco trudny do utrzymania. Jednym z powodów, dla których PostgreSQL działa tak samo dobrze, jak uruchamia zapytania, jest dobry kod („możemy odhaczyć podpowiedzi na naszej liście funkcji porównania dostawców!”), który w rzeczywistości nie zwraca się sam, jeśli chodzi o tworzenie bazy danych na tyle lepiej, aby uzasadnić jego dalsze utrzymanie, jest odrzucana przez politykę. Jeśli to nie zadziała, nie zostanie dodane. A obiektywnie oceniane podpowiedzi są raczej problemem niż rozwiązaniem.
- Rodzaj problemów, które działają podpowiedziami, mogą być błędami optymalizatora. Społeczność PostgreSQL reaguje na prawdziwe błędy w optymalizatorze szybciej niż ktokolwiek inny w branży. Zapytaj, a nie musisz spotykać wielu użytkowników PostgreSQL, zanim znajdziesz takiego, który zgłosił błąd i obserwował, jak został naprawiony następnego dnia.
Teraz główną, całkowicie słuszną odpowiedzią na stwierdzenie braku wskazówek, zwykle od administratorów baz danych, którzy są do nich przyzwyczajeni, jest „no cóż, jak mam poradzić sobie z błędem optymalizatora, kiedy na niego natrafię?” Podobnie jak w przypadku wszystkich prac technicznych w dzisiejszych czasach, zwykle istnieje ogromna presja, aby uzyskać jak najszybszą poprawkę, gdy pojawi się problem z nieprawidłowym zapytaniem.
Jeśli PostgreSQL nie miałby sposobów na poradzenie sobie z tą sytuacją, nie byłoby poważnych produkcyjnych baz danych PostgreSQL . Różnica polega na tym, że rzeczy, które dostosowujesz w tej bazie danych, są bardziej zakorzenione w wpływaniu na decyzje, które optymalizator już podejmuje w dość subtelny sposób, a nie tylko w mówieniu mu, co ma robić. Są to podpowiedzi w dosłownym tego słowa znaczeniu, po prostu nie mają interfejsu użytkownika, który podpowiadałby, że użytkownicy innych baz danych, którzy są nowi w PostgreSQL, będą szukać.
Mając to na uwadze, spójrzmy, co możesz zrobić w PostgreSQL, aby obejść złe plany zapytań i błędy optymalizatora, szczególnie te, które wielu ludzi wydaje się, że można rozwiązać tylko za pomocą wskazówek:
- join_collapse_limit: Ta opcja dostosowuje elastyczność, z jaką optymalizator musi zmieniać kolejność łączeń wielu tabel. Zwykle próbuje każdą możliwą kombinację, gdy złączenia można zmienić (co jest najczęściej, chyba że używasz złączenia zewnętrznego). Obniżenie join_collapse_limit, być może nawet do 1, usuwa część lub całość tej elastyczności. Gdy jest ustawiony na 1, otrzymasz sprzężenia w kolejności, w jakiej je zapisałeś, kropka. Planowanie dużej liczby złączeń jest jedną z najtrudniejszych rzeczy, jakie musi wykonać optymalizator; każde łączenie powiększa błędy w szacunkach i wydłuża czas planowania zapytań. Jeśli podstawowa natura Twoich danych sprawia, że oczywiste jest, jakie łączenie kolejności powinno nastąpić, i nie spodziewasz się, że to się kiedykolwiek zmieni, po ustaleniu właściwej kolejności możesz ją zablokować za pomocą tego parametru.
- random_page_cost: Domyślnie 4.0, ten parametr określa koszt szukania na dysku losowej strony na dysku w stosunku do wartości referencyjnej 1.0. Teraz, w rzeczywistości, jeśli zmierzysz stosunek losowych operacji we/wy do sekwencyjnych na zwykłych dyskach twardych, okaże się, że liczba ta jest bliższa 50. Dlaczego więc 4.0? Po pierwsze, ponieważ sprawdza się to lepiej niż większe wartości w testach społecznościowych. Po drugie, w wielu przypadkach w szczególności dane indeksowe będą buforowane w pamięci, dzięki czemu efektywny koszt odczytania tych wartości będzie niższy. Jeśli na przykład twój indeks jest w 90% buforowany w pamięci RAM, oznacza to, że przez 10% czasu wykonasz operację, która jest 50 razy droższa; dzięki temu Twój efektywny koszt random_page_cost wynosi około 5. Taka sytuacja w świecie rzeczywistym sprawia, że wartość domyślna ma sens tam, gdzie się znajduje. Zwykle widzę, że popularne indeksy pobierają>95% pamięci podręcznej w pamięci. Jeśli twój indeks jest w rzeczywistości znacznie bardziej prawdopodobny niż to, że wszystkie znajdują się w pamięci RAM, zmniejszenie random_page_cost do poziomu nieco powyżej 1,0 może być rozsądnym wyborem, aby odzwierciedlić, że nie jest droższy niż jakikolwiek inny odczyt. Jednocześnie losowe wyszukiwania w naprawdę obciążonych systemach mogą być znacznie droższe, niż można by oczekiwać, patrząc na symulacje jednego użytkownika. Musiałem ustawić wartość random_page_cost na poziomie 60, aby baza danych przestała używać indeksów, gdy planista błędnie oszacował ich koszt. Zazwyczaj taka sytuacja wynika z błędu oszacowania czułości po stronie planisty – jeśli skanujesz więcej niż około 20% tabeli, planista wie, że użycie skanowania sekwencyjnego będzie znacznie wydajniejsze niż skanowanie indeksowe. Brzydka sytuacja, w której musiałem wymusić takie zachowanie znacznie wcześniej, miała miejsce, gdy planista oczekiwał, że zostanie zwrócony 1% wierszy, ale w rzeczywistości było to bliższe 15%.
- work_mem: Dopasowuje ilość pamięci dostępnej dla zapytań wykonujących sortowanie, mieszanie i podobne operacje oparte na pamięci. Jest to tylko przybliżona wskazówka dla zapytań, a nie sztywny limit, a pojedynczy klient może w efekcie użyć wielokrotności work_mem podczas uruchamiania zapytania. W związku z tym należy uważać, aby nie ustawić tej wartości zbyt wysoko w pliku postgresql.conf. To, co możesz zrobić zamiast tego, ustawia to przed uruchomieniem zapytania, które naprawdę korzysta z posiadania dodatkowej pamięci do przechowywania danych sortowania lub mieszania. Czasami możesz znaleźć te zapytania z rejestrowania powolnych przy użyciu log_min_duration_statement. Możesz je również znaleźć, włączając log_temp_files, które będą rejestrować za każdym razem, gdy work_mem jest zbyt mały, a zatem operacje sortowania rozlewają się na dysk zamiast zapisywać się w pamięci.
- OFFSET 0:PostgreSQL zmieni kolejność podzapytań w formę złączenia, dzięki czemu będzie mógł użyć logiki zwykłej kolejności złączeń do jej optymalizacji. W niektórych przypadkach ta decyzja może być naprawdę zła, ponieważ z jakiegoś powodu rzeczy, które ludzie piszą jako podzapytania, wydają się nieco trudniejsze do oszacowania (mówię to na podstawie liczby takich kłopotliwych zapytań, które widzę). Jedną podstępną sztuczką, którą możesz zrobić, aby zapobiec tej logice, jest umieszczenie PRZESUNIĘCIA 0 na końcu podzapytania. Nie zmienia to żadnych wyników, ale wstawienie typu węzła Ograniczenie użytego do wykonania PRZESUNIĘCIA zapobiegnie przegrupowaniu. Podzapytanie będzie wtedy zawsze wykonywane w sposób, w jaki większość ludzi tego oczekuje – jako własny izolowany węzeł zapytania.
- enable_seqscan, enable_indexscan, enable_bitmapscan: Wyłączenie jednej z tych funkcji wyszukiwania wierszy w tabeli jest dość dużym wyzwaniem, aby zdecydowanie zalecać unikanie tego typu skanowania (nie zawsze mu się to uniemożliwia — jeśli nie ma możliwości wykonania planu, ale seqscan, otrzymasz seqscan, nawet jeśli parametry są wyłączone). Najważniejszą rzeczą, do której je polecam, nie jest naprawianie zapytań, ale eksperymentowanie z EXPLAIN i sprawdzenie, dlaczego preferowany był inny typ skanowania.
- enable_nestloop, enable_hashjoin, enable_mergejoin: Jeśli podejrzewasz, że Twoim problemem jest typ używanego łączenia, a nie sposób odczytywania tabel, spróbuj wyłączyć typ, który widzisz w swoim planie za pomocą jednego z tych parametrów, a następnie uruchom polecenie WYJAŚNIJ ponownie. Błędy w szacunkach wrażliwości mogą z łatwością sprawić, że łączenie wydaje się mniej lub bardziej wydajne niż jest w rzeczywistości. I znowu, zobaczenie, jak zmienia się plan z wyłączoną obecną metodą łączenia, może być bardzo pouczające, dlaczego w pierwszej kolejności zdecydował się na tę metodę.
- enable_hashagg, enable_material: te funkcje są stosunkowo nowe w PostgreSQL. Agresywne korzystanie z funkcji Hash Aggregation zostało wprowadzone w wersji 8.4, a bardziej agresywna materializacja w wersji 9.0. Jeśli widzisz tego typu węzły w swoim wyniku WYJŚĆ
i wydaje się, że robią coś złego, ponieważ ten kod jest o wiele nowszy, jest trochę bardziej prawdopodobne, że ma ograniczenie lub błąd niż niektóre starsze funkcje. Jeśli miałeś plan, który działał dobrze w starszych wersjach PostgreSQL, ale używa jednego z tych typów węzłów i wydaje się, że w rezultacie działa znacznie gorzej, wyłączenie tych funkcji może czasami przywrócić wcześniejsze zachowanie, a także rzucić nieco światła na dlaczego optymalizator zrobił coś złego jako użyteczną informację zwrotną. Zwróć uwagę, że jest to zazwyczaj sposób, w jaki bardziej zaawansowane funkcje są wprowadzane do PostgreSQL:z opcją wyłączenia go w celu rozwiązywania problemów, jeśli okaże się, że istnieje regresja planu w stosunku do tego, jak wykonywały rzeczy wcześniejsze wersje. - cursor_tuple_fraction: Jeśli nie zamierzasz odczytywać wszystkich wierszy z zapytania, użyj kursora, aby to zaimplementować. W takim przypadku optymalizator próbuje ustalić priorytet, czy szybko zwraca pierwszy wiersz, czy woli zoptymalizować całe zapytanie na podstawie tego parametru. Domyślnie baza danych zakłada, że po użyciu kursora ponownie odczytasz 10% zapytania. Dostosowanie tego parametru pozwala nastawić go w kierunku oczekiwania, że będziesz czytać mniej lub więcej.
Wszystkie te parametry i poprawki zapytań powinny być brane pod uwagę przy selekcji. Nie chcesz działać z nimi na zawsze (może z wyjątkiem join_collapse_limit). Używasz ich, aby wyjść z zacięcia, a potem, miejmy nadzieję, dowiesz się, jaka jest prawdziwa przyczyna złego planu – złe statystyki, ograniczenie/błąd optymalizatora lub coś innego – a następnie rozwiążesz problem z tego kierunku. Im bardziej popychasz zachowanie optymalizatora w określonym kierunku, tym bardziej jesteś narażony na przyszłe zmiany w danych, które powodują, że nie są one już poprawne. Jeśli użyjesz ich właściwie, jako sposobu na zbadanie, dlaczego masz zły plan (podejście, którego użyłem w rozdziale poświęconym optymalizacji zapytań w PostgreSQL 9.0 High Performance), sposób, w jaki podajesz wskazówki w PostgreSQL, powinien skutkować opuszczaniem każdego uruchomienia. ze złym zachowaniem optymalizatora, trochę bardziej sprytny, jak uniknąć tego typu problemów w przyszłości