Zapytania równoległe w PostgreSQL

Zapytania równoległe w PostgreSQL
W nowoczesnych procesorach jest wiele rdzeni. Przez lata aplikacje wysyłały zapytania do baz danych równolegle. Jeśli jest to raportowe zapytanie do wielu wierszy w tabeli, wykonuje się szybciej, gdy angażuje wiele rdzeni, a w PostgreSQL jest to możliwe od wersji 9.6.

Zajęło to 3 lata, aby zrealizować funkcję zapytań równoległych — trzeba było przepisać kod na różnych etapach wykonywania zapytań. W PostgreSQL 9.6 pojawiła się infrastruktura do dalszego ulepszania kodu. W kolejnych wersjach również inne typy zapytań są wykonywane równolegle.

Ograniczenia

  • Nie włączaj równoległego wykonywania, jeśli wszystkie rdzenie są już zajęte, w przeciwnym razie inne zapytania będą spowalniane.
  • Najważniejsze, że równoległe przetwarzanie z wysokimi wartościami WORK_MEM zajmuje dużo pamięci — każde połączenie haszowe lub sortowanie zajmuje pamięć w objętości work_mem.
  • Zapytania OLTP o niskim opóźnieniu nie mogą być przyspieszone przez równoległe wykonywanie. A jeśli zapytanie zwraca jeden wiersz, równoległe przetwarzanie tylko go spowolni.
  • Programiści lubią korzystać z benchmarka TPC-H. Może masz podobne zapytania do idealnego równoległego wykonywania.
  • Tylko zapytania SELECT bez blokowania predykatami są wykonywane równolegle.
  • Czasami odpowiednia indeksacja jest lepsza od sekwencyjnego skanowania tabeli w trybie równoległym.
  • Wstrzymywanie zapytań i kursory nie są obsługiwane.
  • Funkcje okienne i funkcje agregujące uporządkowanych zbiorów nie są równoległe.
  • Nie zyskujesz nic w obciążeniu wejścia-wyjścia.
  • Nie ma równoległych algorytmów sortowania. Ale zapytania z sortowaniem mogą być wykonywane równolegle w niektórych aspektach.
  • Zastąp CTE (WITH …) zagnieżdżonym SELECT, aby włączyć równoległe przetwarzanie.
  • Wrappery danych zewnętrznych na razie nie obsługują równoległego przetwarzania (a mogłyby!).
  • FULL OUTER JOIN nie jest obsługiwany.
  • max_rows wyłącza równoległe przetwarzanie.
  • Jeśli w zapytaniu jest funkcja, która nie jest oznaczona jako PARALLEL SAFE, zostanie wykonane w jednym wątku.
  • Poziom izolacji transakcji SERIALIZABLE wyłącza równoległe przetwarzanie.

Środowisko testowe

Programiści PostgreSQL próbowali skrócić czas reakcji zapytań z benchmarka TPC-H. Pobierz benchmark i dostosuj go do PostgreSQL. To nieoficjalne użycie benchmarku TPC-H — nie do porównywania baz danych ani sprzętu.

  1. Pobierz TPC-H_Tools_v2.17.3.zip (lub nowszą wersję) z oficjalnej strony TPC.
  2. Zmień nazwę makefile.suite na Makefile i zmodyfikuj, jak opisano tutaj: https://github.com/tvondra/pg_tpch . Skompiluj kod poleceniem make.
  3. Wygeneruj dane: .\/dbgen -s 10 tworzy bazę danych o wielkości 23 GB. To wystarczy, żeby zobaczyć różnicę w wydajności zapytań równoległych i nierównoległych.
  4. Przekonwertuj pliki tbl do csv za pomocą i sed.
  5. Sklonuj repozytorium pg_tpch i skopiuj pliki csv do pg_tpch\/dss\/data.
  6. Utwórz zapytania poleceniem qgen.
  7. Załaduj dane do bazy poleceniem .\/tpch.sh.

Równoległe sekwencyjne skanowanie

Może być szybsze nie przez równoległe odczyty, lecz dlatego, że dane są rozproszone po wielu rdzeniach CPU. W nowoczesnych systemach operacyjnych pliki danych PostgreSQL są dobrze buforowane. Dzięki wstępnym odczytom można uzyskać z pamięci więcej bloków niż żąda demon PG. Dlatego wydajność zapytania nie jest ograniczona przez dyskowe operacje wejścia-wyjścia. Wykorzystuje cykle CPU, aby:

  • czytać linie jedna po drugiej z stron tabeli;
  • porównywać wartości wierszy i warunki WHERE.

Wykonaj proste zapytanie select:

tpch=# explain analyze select l_quantity as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Seq Scan on lineitem (cost=0.00..1964772.00 rows=58856235 width=5) (actual time=0.014..16951.669 rows=58839715 loops=1)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 1146337
Planning Time: 0.203 ms
Execution Time: 19035.100 ms

Sekwencyjne skanowanie zwraca zbyt wiele wierszy bez agregacji, tak że zapytanie jest realizowane przez jeden rdzeń CPU.

Jeśli dodasz SUM(), widać, że dwa procesy pomogą przyspieszyć zapytanie:

explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate  Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Równoległe skanowanie sekwencyjne na lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 ms

Równoległa agregacja

Węzeł „Parallel Seq Scan” generuje wiersze do częściowej agregacji. Węzeł „Partial Aggregate” ogranicza te wiersze za pomocą SUM(). Na końcu zliczacz SUM z każdego procesu roboczego jest zbierany przez węzeł „Gather”.

Ostateczny wynik jest obliczany przez węzeł „Finalize Aggregate”. Jeśli masz własne funkcje agregacji, nie zapomnij oznaczyć ich jako „parallel safe”.

Liczba procesów roboczych

Liczbę procesów roboczych można zwiększyć bez ponownego uruchamiania serwera:

explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate  Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Równoległe skanowanie sekwencyjne na lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 ms

Co tu się dzieje? Liczba procesów roboczych wzrosła dwukrotnie, a zapytanie stało się jedynie 1,6599 razy szybsze. Obliczenia są interesujące. Mieliśmy 2 procesy robocze i 1 lidera. Po zmianie mieliśmy 4+1.

Nasze maksymalne przyspieszenie dzięki przetwarzaniu równoległemu: 5/3 = 1,66(6) razy.

Jak to działa?

Procesy

Wykonanie zapytania zawsze zaczyna się od procesu lidera. Lider wykonuje wszystkie operacje nieparalne oraz część przetwarzania równoległego. Inne procesy, które wykonują te same zapytania, nazywają się procesami roboczymi. Przetwarzanie równoległe używa infrastruktury dynamicznych procesów roboczych w tle (od wersji 9.4). Ponieważ inne części PostgreSQL korzystają z procesów, a nie wątków, zapytanie z 3 procesami roboczymi mogło być 4 razy szybsze niż tradycyjne przetwarzanie.

Interakcja

Procesy robocze komunikują się z liderem za pośrednictwem kolejki wiadomości (opartej na pamięci współdzielonej). Każdy proces ma 2 kolejki: jedną do błędów i jedną do krotek.

Ile potrzebujesz procesów roboczych?

Minimalne ograniczenie ustawia parametr max_parallel_workers_per_gather. Potem wykonawca zapytań pobiera procesy robocze z puli, ograniczonej przez parametr max_parallel_workers size. Ostatnie ograniczenie to max_worker_processes, czyli całkowita liczba procesów w tle.

Jeśli nie uda się przydzielić procesu roboczego, przetwarzanie będzie jednoprosesowe.

Planista zapytań może zmniejszyć liczbę procesów roboczych w zależności od rozmiaru tabeli lub indeksu. Do tego są parametry min_parallel_table_scan_size i min_parallel_index_scan_size.

set min_parallel_table_scan_size='8MB'
8MB tabela => 1 proces roboczy
24MB tabela => 2 procesy robocze
72MB tabela => 3 procesy robocze
x => log(x / min_parallel_table_scan_size) / log(3) + 1 proces roboczy

Za każdym razem, gdy tabela jest 3 razy większa niż min_parallel_(index|table)_scan_size, Postgres dodaje proces roboczy. Liczba procesów roboczych nie opiera się na kosztach. Cykliczne zależności utrudniają skomplikowane realizacje. Zamiast tego planista stosuje proste zasady.

W praktyce te zasady nie zawsze sprawdzają się w produkcji, dlatego można zmienić liczbę procesów roboczych dla konkretnej tabeli: ALTER TABLE … SET (parallel_workers = N).

Dlaczego nie używa się przetwarzania równoległego?

Oprócz długiej listy ograniczeń istnieją również kontrole kosztów:

parallel_setup_cost — aby uniknąć przetwarzania równoległego w przypadku krótkich zapytań. Ten parametr oszacowuje czas potrzebny na przygotowanie pamięci, uruchomienie procesu i początkową wymianę danych.

parallel_tuple_cost: komunikacja lidera z pracownikami może być opóźniona proporcjonalnie do liczby krotek od procesów roboczych. Ten parametr oblicza koszty wymiany danych.

Połączenia zagnieżdżonych pętli — Nested Loop Join

PostgreSQL 9.6+ może wykonywać zagnieżdżone pętle równolegle — to prosta operacja.

explain (costs off) select c_custkey, count(o_orderkey)
 from customer left outer join orders on
 c_custkey = o_custkey and o_comment not like '%specialposits%'
 group by c_custkey;
 PLAN ZAPYTANIA
--------------------------------------------------------------------------------------
 Finalize GroupAggregate
 Klucz grupy: customer.c_custkey
 -> Gather Merge
 Zaplanowani pracownicy: 4
 -> Partial GroupAggregate
 Klucz grupy: customer.c_custkey
 -> Zagnieżdżone połączenie lewostronne
 -> Równoległe skanowanie indeksu tylko na customer
 -> Skanowanie indeksu przy użyciu idx_orders_custkey na orders
 Warunek indeksu: (customer.c_custkey = o_custkey)
 Filtr: ((o_comment)::text !~~ '%specialposits%'::text)

Agregacja odbywa się na ostatnim etapie, więc Nested Loop Left Join — to operacja równoległa. Parallel Index Only Scan pojawił się dopiero w wersji 10. Działa w podobny sposób jak równoległe skanowanie sekwencyjne. Warunek c_custkey = o_custkey odczytuje jedno zamówienie dla każdego klienta. Tak więc nie jest to równoległe.

Hash join — Hash Join

Każdy proces roboczy tworzy swoją tablicę haszującą do PostgreSQL 11. A jeśli tych procesów jest więcej niż cztery, wydajność nie wzrośnie. W nowej wersji tablica haszująca jest wspólna. Każdy proces roboczy może używać WORK_MEM, aby utworzyć tablicę haszującą.

wybierz
        l_shipmode,
        suma(przypadek
                gdy o_orderpriority = '1-URGENT'
                        lub o_orderpriority = '2-HIGH'
                        wtedy 1
                w przeciwnym razie 0
        kończ) jako high_line_count,
        suma(przypadek
                gdy o_orderpriority  '1-URGENT'
                        i o_orderpriority  '2-HIGH'
                        wtedy 1
                w przeciwnym razie 0
        kończ) jako low_line_count
z
        zamówienia,
        pozycja zamówienia
gdzie
        o_orderkey = l_orderkey
        i l_shipmode w ('MAIL', 'AIR')
        i l_commitdate < l_receiptdate
        i l_shipdate = data '1996-01-01'
        i l_receiptdate < data '1996-01-01' + interwał '1' rok
grupuj według
        l_shipmode
zamów według
        l_shipmode
LIMIT 1;
                                                                                                                                    PLAN ZAPYTANIA
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (koszt=1964755.66..1964961.44 wiersze=1 szerokość=27) (rzeczywisty czas=7579.592..7922.997 wiersze=1 pętle=1)
   ->  Finalizuj GrupęAgregat  (koszt=1964755.66..1966196.11 wiersze=7 szerokość=27) (rzeczywisty czas=7579.590..7579.591 wiersze=1 pętle=1)
         Klucz grupy: lineitem.l_shipmode
         ->  Zgromadź Merguj  (koszt=1964755.66..1966195.83 wiersze=28 szerokość=27) (rzeczywisty czas=7559.593..7922.319 wiersze=6 pętle=1)
               Planowane pracownicy: 4
               Uruchomieni pracownicy: 4
               ->  Częściowy GrupujAgregat  (koszt=1963755.61..1965192.44 wiersze=7 szerokość=27) (rzeczywisty czas=7548.103..7564.592 wiersze=2 pętle=5)
                     Klucz grupy: lineitem.l_shipmode
                     ->  Sortuj  (koszt=1963755.61..1963935.20 wiersze=71838 szerokość=27) (rzeczywisty czas=7530.280..7539.688 wiersze=62519 pętle=5)
                           Klucz sortowania: lineitem.l_shipmode
                           Metoda sortowania: zewnętrzna scalanie  Dysk: 2304kB
                           Pracownik 0:  Metoda sortowania: zewnętrzna scalanie  Dysk: 2064kB
                           Pracownik 1:  Metoda sortowania: zewnętrzna scalanie  Dysk: 2384kB
                           Pracownik 2:  Metoda sortowania: zewnętrzna scalanie  Dysk: 2264kB
                           Pracownik 3:  Metoda sortowania: zewnętrzna scalanie  Dysk: 2336kB
                           ->  Równoległe Haszowanie Dołączenie  (koszt=382571.01..1957960.99 wiersze=71838 szerokość=27) (rzeczywisty czas=7036.917..7499.692 wiersze=62519 pętle=5)
                                 Warunek haszowania: (lineitem.l_orderkey = orders.o_orderkey)
                                 ->  Równoległe Przeszukiwanie sekwecyjnie na pozycjach  (koszt=0.00..1552386.40 wiersze=71838 szerokość=19) (rzeczywisty czas=0.583..4901.063 wiersze=62519 pętle=5)
                                       Filtr: ((l_shipmode = ANY ('{MAIL,AIR}'::bpchar[])) AND (l_commitdate < l_receiptdate) AND (l_shipdate = '1996-01-01'::data) AND (l_receiptdate < '1997-01-01 00:00:00'::znacznik bez strefy czasowej))
                                       Wiersze usunięte przez filtr: 11934691
                                 ->  Równoległe Hasz  (koszt=313722.45..313722.45 wiersze=3750045 szerokość=20) (rzeczywisty czas=2011.518..2011.518 wiersze=3000000 pętle=5)
                                       Kosze: 65536  Partii: 256  Użycie pamięci: 3840kB
                                       ->  Równoległe Przeszukiwanie sekwecyjnie na zamówienia  (koszt=0.00..313722.45 wiersze=3750045 szerokość=20) (rzeczywisty czas=0.029..995.948 wiersze=3000000 pętle=5)
 Czas planowania: 0.977 ms
 Czas wykonania: 7923.770 ms

Zapytanie 12 z TPC-H ilustruje równoległe połączenie haszowe. Każdy proces uczestniczy w tworzeniu wspólnej tabeli haszowej.

Połączenie złączeniem — Merge Join

Połączenie złączeniem jest z natury nieprzyspieszone. Nie martw się, jeśli to ostatni etap zapytania — nadal może być realizowane równolegle.

-- Zapytanie 2 z TPC-H
wyjaśnij (koszty wyłączone) wybierz s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment
z    part, supplier, partsupp, nation, region
gdzie
        p_partkey = ps_partkey
        i s_suppkey = ps_suppkey
        i p_size = 36
        i p_type podobne '%BRASS'
        i s_nationkey = n_nationkey
        i n_regionkey = r_regionkey
        i r_name = 'AMERICA'
        i ps_supplycost = (
                wybierz
                        min(ps_supplycost)
                z    partsupp, supplier, nation, region
                gdzie
                        p_partkey = ps_partkey
                        i s_suppkey = ps_suppkey
                        i s_nationkey = n_nationkey
                        i n_regionkey = r_regionkey
                        i r_name = 'AMERICA'
        )
kolejność według s_acctbal malejąco, n_name, s_name, p_partkey
LIMIT 100;
                                                PLAN ZAPYTANIA
----------------------------------------------------------------------------------------------------------
 Limit
   -&gt;  Sort
         Klucz Sortowania: supplier.s_acctbal MALEJĄCO, nation.n_name, supplier.s_name, part.p_partkey
         -&gt;  Połączenie Scalające
               Warunek Scalania: (part.p_partkey = partsupp.ps_partkey)
               Filtr Połączenia: (partsupp.ps_supplycost = (SubPlan 1))
               -&gt;  Scalanie Gromadzące
                     Zaplanowani pracownicy: 4
                     -&gt;  Równoległe Skany Indeksu przy użyciu <strong>part_pkey</strong> na części
                           Filtr: (((p_type)::text ~~ '%BRASS'::text) I (p_size = 36))
               -&gt;  Materializacja
                     -&gt;  Sort
                           Klucz Sortowania: partsupp.ps_partkey
                           -&gt;  Pętla Zagnieżdżona
                                 -&gt;  Pętla Zagnieżdżona
                                       Filtr Połączenia: (nation.n_regionkey = region.r_regionkey)
                                       -&gt;  Se kw scan na region
                                             Filtr: (r_name = 'AMERICA'::bpchar)
                                       -&gt;  Połączenie Haszowe
                                             Warunek Haszowy: (supplier.s_nationkey = nation.n_nationkey)
                                             -&gt;  Seq Scan na supplier
                                             -&gt;  Hasz
                                                   -&gt;  Seq Scan na nation
                                 -&gt;  Scan Indeksu przy użyciu idx_partsupp_suppkey na partsupp
                                       Warunek Indeksu: (ps_suppkey = supplier.s_suppkey)
               SubPlan 1
                 -&gt;  Agregacja
                       -&gt;  Pętla Zagnieżdżona
                             Filtr Połączenia: (nation_1.n_regionkey = region_1.r_regionkey)
                             -&gt;  Seq Scan na region region_1
                                   Filtr: (r_name = 'AMERICA'::bpchar)
                             -&gt;  Pętla Zagnieżdżona
                                   -&gt;  Pętla Zagnieżdżona
                                         -&gt;  Scan Indeksu przy użyciu idx_partsupp_partkey na partsupp partsupp_1
                                               Warunek Indeksu: (part.p_partkey = ps_partkey)
                                         -&gt;  Scan Indeksu przy użyciu supplier_pkey na supplier supplier_1
                                               Warunek Indeksu: (s_suppkey = partsupp_1.ps_suppkey)
                                   -&gt;  Scan Indeksu przy użyciu nation_pkey na nation nation_1
                                         Warunek Indeksu: (n_nationkey = supplier_1.s_nationkey)

Węzeł «Merge Join» znajduje się nad «Gather Merge». Tak więc, złączenie nie korzysta z przetwarzania równoległego. Jednak węzeł «Parallel Index Scan» nadal pomaga z segmentem. part_pkey.

Połączenie według sekcji

W PostgreSQL 11 połączenie według sekcji domyślnie wyłączone: ma bardzo kosztowne planowanie. Tabele o podobnym podziałach można łączyć sekcja po sekcji. W ten sposób Postgres wykorzysta mniejsze tabele haszowe. Każde połączenie sekcji może być równoległe.

tpch=# set enable_partitionwise_join=t;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
                    PLAN ZAPYTANIA
---------------------------------------------------
 Dodaj
   ->  Złączenie haszowe
         Warunek hasza: (t2.b = t1.a)
         ->  Skanowanie sekwencyjne na prt2_p1 t2
               Filtr: ((b >= 0) AND (b <= 10000))
         ->  Hasz
               ->  Skanowanie sekwencyjne na prt1_p1 t1
                     Filtr: (b = 0)
   ->  Złączenie haszowe
         Warunek hasza: (t2_1.b = t1_1.a)
         ->  Skanowanie sekwencyjne na prt2_p2 t2_1
               Filtr: ((b >= 0) AND (b <= 10000))
         ->  Hasz
               ->  Skanowanie sekwencyjne na prt1_p2 t1_1
                     Filtr: (b = 0)
tpch=# set parallel_setup_cost = 1;
tpch=# set parallel_tuple_cost = 0.01;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
                        PLAN ZAPYTANIA
-----------------------------------------------------------
 Zbierz
   Zaplanowani pracownicy: 4
   ->  Równoległy dodatek
         ->  Równoległe złączenie haszowe
               Warunek hasza: (t2_1.b = t1_1.a)
               ->  Równoległe skanowanie sekwencyjne na prt2_p2 t2_1
                     Filtr: ((b >= 0) AND (b <= 10000))
               ->  Równoległy hasz
                     ->  Równoległe skanowanie sekwencyjne na prt1_p2 t1_1
                           Filtr: (b = 0)
         ->  Równoległe złączenie haszowe
               Warunek hasza: (t2.b = t1.a)
               ->  Równoległe skanowanie sekwencyjne na prt2_p1 t2
                     Filtr: ((b >= 0) AND (b <= 10000))
               ->  Równoległy hasz
                     ->  Równoległe skanowanie sekwencyjne na prt1_p1 t1
                           Filtr: (b = 0)

Główna kwestia, połączenie według sekcji może być równoległe, tylko jeśli te sekcje są wystarczająco duże.

Równoległy dodatek — Parallel Append

Parallel Append może być używany zamiast różnych bloków w różnych procesach roboczych. Zwykle jest to związane z zapytaniami UNION ALL. Wadą jest mniejszy poziom równoległości, ponieważ każdy proces roboczy przetwarza tylko 1 zapytanie.

Tutaj uruchomiono 2 procesy robocze, chociaż włączono 4.

tpch=# explain (costs off) select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day union all select sum(l_quantity) as sum_qty from lineitem where l_shipdate   Parallel Append
         ->  Aggregate
               ->  Seq Scan on lineitem
                     Filter: (l_shipdate   Aggregate
               ->  Seq Scan on lineitem lineitem_1
                     Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)

Najważniejsze zmienne

  • WORK_MEM ogranicza ilość pamięci dla każdego procesu, nie tylko dla zapytań: work_mem procesy połączenia = bardzo dużo pamięci.
  • max_parallel_workers_per_gather — ile procesów roboczych program będzie używać do równoległego przetwarzania z planu.
  • max_worker_processes — dostosowuje ogólną liczbę procesów roboczych do liczby rdzeni CPU na serwerze.
  • max_parallel_workers — to samo, ale dla równoległych procesów roboczych.

Podsumowanie

Od wersji 9.6 równoległe przetwarzanie może znacznie poprawić wydajność skomplikowanych zapytań, które skanują wiele wierszy lub indeksów. W PostgreSQL 10 równoległe przetwarzanie jest domyślnie włączone. Nie zapomnij wyłączyć go na serwerach z dużym obciążeniem OLTP. Sekwencyjne skany lub skany indeksów zużywają bardzo dużo zasobów. Jeśli nie wykonujesz raportu na całym zestawie danych, zapytania można uczynić bardziej efektywnymi, po prostu dodając brakujące indeksy lub korzystając z odpowiedniego partycjonowania.

Linki

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster