PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak najlepiej wykorzystać indeksy PostgreSQL

W świecie Postgresa indeksy są niezbędne do sprawnego poruszania się po pamięci tabledata (zwanej też „stertą”). Postgres nie utrzymuje klastrowania dla sterty, a architektura MVCC prowadzi do wielu wersji tej samej krotki. Tworzenie i utrzymywanie skutecznych i wydajnych indeksów do obsługi aplikacji to podstawowa umiejętność.

Czytaj dalej, aby zapoznać się z kilkoma wskazówkami dotyczącymi optymalizacji i poprawy wykorzystania indeksów we wdrożeniu.

Uwaga:zapytania pokazane poniżej są uruchamiane w niezmodyfikowanej przykładowej bazie danych.

Użyj indeksów pokrycia

Rozważ zapytanie, aby pobrać e-maile wszystkich nieaktywnych klientów. Klient stół ma aktywny kolumna, a zapytanie jest proste:

pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                        QUERY PLAN
-----------------------------------------------------------
 Seq Scan on customer  (cost=0.00..16.49 rows=15 width=32)
   Filter: (active = 0)
(2 rows)

Zapytanie wywołuje pełne sekwencyjne skanowanie tabeli klientów. Stwórzmy indeks w aktywnej kolumnie:

pagila=# CREATE INDEX idx_cust1 ON customer(active);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Index Scan using idx_cust1 on customer  (cost=0.28..12.29 rows=15 width=32)
   Index Cond: (active = 0)
(2 rows)

To pomaga, a skanowanie sekwencyjne stało się „skanowaniem indeksowym”. Oznacza to, że Postgres przeskanuje indeks „idx_cust1”, a następnie przeszuka stos tabeli, aby odczytać wartości innych kolumn (w tym przypadku e-mail kolumna), których wymaga zapytanie.

PostgreSQL 11 wprowadził indeksy obejmujące. Ta funkcja umożliwia uwzględnienie jednej lub więcej dodatkowych kolumn w samym indeksie – to znaczy, że wartości tych dodatkowych kolumn są przechowywane w magazynie danych indeksu.

Gdybyśmy mieli skorzystać z tej funkcji i uwzględnić wartość e-mail wewnątrz indeksu, Postgres nie będzie musiał zaglądać do stosu tabeli, aby uzyskać wartość e-mail . Zobaczmy, czy to zadziała:

pagila=# CREATE INDEX idx_cust2 ON customer(active) INCLUDE (email);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Index Only Scan using idx_cust2 on customer  (cost=0.28..12.29 rows=15 width=32)
   Index Cond: (active = 0)
(2 rows)

„Skanowanie tylko indeksu” mówi nam, że zapytanie jest teraz całkowicie spełnione przez sam indeks, co potencjalnie pozwala uniknąć wszystkich operacji we/wy dysku w celu odczytania sterty tabeli.

Indeksy pokrywające są obecnie dostępne tylko dla indeksów B-Tree. Ponadto koszt utrzymania indeksu pokrycia jest oczywiście wyższy niż zwykłego.

Użyj indeksów częściowych

Indeksy częściowe indeksują tylko podzbiór wierszy w tabeli. Dzięki temu indeksy mają mniejszy rozmiar i można je szybciej przeglądać.

Załóżmy, że musimy uzyskać listę e-maili klientów znajdujących się w Kalifornii. Zapytanie to:

SELECT c.email FROM customer c
JOIN address a ON c.address_id = a.address_id
WHERE a.district = 'California';

który ma plan zapytań, który obejmuje skanowanie obu połączonych tabel:

pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
                              QUERY PLAN
----------------------------------------------------------------------
 Hash Join  (cost=15.65..32.22 rows=9 width=32)
   Hash Cond: (c.address_id = a.address_id)
   ->  Seq Scan on customer c  (cost=0.00..14.99 rows=599 width=34)
   ->  Hash  (cost=15.54..15.54 rows=9 width=4)
         ->  Seq Scan on address a  (cost=0.00..15.54 rows=9 width=4)
               Filter: (district = 'California'::text)
(6 rows)

Zobaczmy, co daje nam zwykły indeks:

pagila=# CREATE INDEX idx_address1 ON address(district);
CREATE INDEX
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Hash Join  (cost=12.98..29.55 rows=9 width=32)
   Hash Cond: (c.address_id = a.address_id)
   ->  Seq Scan on customer c  (cost=0.00..14.99 rows=599 width=34)
   ->  Hash  (cost=12.87..12.87 rows=9 width=4)
         ->  Bitmap Heap Scan on address a  (cost=4.34..12.87 rows=9 width=4)
               Recheck Cond: (district = 'California'::text)
               ->  Bitmap Index Scan on idx_address1  (cost=0.00..4.34 rows=9 width=0)
                     Index Cond: (district = 'California'::text)
(8 rows)

Skanowanie adresu został zastąpiony skanowaniem indeksu nad idx_address1 i skan stosu adresów.

Zakładając, że jest to częste zapytanie i wymaga optymalizacji, możemy użyć indeksu cząstkowego, który indeksuje tylko te wiersze adresu, w których dzielnica to „Kalifornia”:

pagila=# CREATE INDEX idx_address2 ON address(address_id) WHERE district='California';
CREATE INDEX
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
                                           QUERY PLAN
------------------------------------------------------------------------------------------------
 Hash Join  (cost=12.38..28.96 rows=9 width=32)
   Hash Cond: (c.address_id = a.address_id)
   ->  Seq Scan on customer c  (cost=0.00..14.99 rows=599 width=34)
   ->  Hash  (cost=12.27..12.27 rows=9 width=4)
         ->  Index Only Scan using idx_address2 on address a  (cost=0.14..12.27 rows=9 width=4)
(5 rows)

Zapytanie odczytuje teraz tylko indeks idx_address2 i nie dotyka tabeliadres .

Użyj indeksów wielowartościowych

Niektóre kolumny, które wymagają indeksowania, mogą nie mieć skalarnego typu danych. Typy kolumn, takie jak jsonb , tablice i tsvector mieć złożone lub wiele wartości. Jeśli potrzebujesz indeksować takie kolumny, zwykle jest tak, że musisz przeszukać również poszczególne wartości w tych kolumnach.

Spróbujmy znaleźć wszystkie tytuły filmów, które zawierają sceny zza kulis. Film tabela ma kolumnę z tablicą tekstową o nazwie special_features , który zawiera element tablicy tekstowej Za kulisami jeśli film ma tę funkcję. Aby znaleźć wszystkie takie filmy, musimy zaznaczyć wszystkie wiersze zawierające „Za kulisami” wdowolnym wartości tablicy specjalne_features :

SELECT title FROM film WHERE special_features @> '{"Behind The Scenes"}';

Operator zamknięcia @> sprawdza, czy lewa strona jest nadzbiorem prawej strony.

Oto plan zapytań:

pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
                           QUERY PLAN
-----------------------------------------------------------------
 Seq Scan on film  (cost=0.00..67.50 rows=5 width=15)
   Filter: (special_features @> '{"Behind The Scenes"}'::text[])
(2 rows)

co wymaga pełnego skanu stosu, kosztem 67.

Zobaczmy, czy pomaga zwykły indeks B-Tree:

pagila=# CREATE INDEX idx_film1 ON film(special_features);
CREATE INDEX
pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
                           QUERY PLAN
-----------------------------------------------------------------
 Seq Scan on film  (cost=0.00..67.50 rows=5 width=15)
   Filter: (special_features @> '{"Behind The Scenes"}'::text[])
(2 rows)

Indeks nie jest nawet brany pod uwagę. Indeks B-Tree nie ma pojęcia, że ​​w wartości, którą zaindeksował, znajdują się pojedyncze elementy.

Potrzebujemy indeksu GIN.

pagila=# CREATE INDEX idx_film2 ON film USING GIN(special_features);
CREATE INDEX
pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
                                QUERY PLAN
---------------------------------------------------------------------------
 Bitmap Heap Scan on film  (cost=8.04..23.58 rows=5 width=15)
   Recheck Cond: (special_features @> '{"Behind The Scenes"}'::text[])
   ->  Bitmap Index Scan on idx_film2  (cost=0.00..8.04 rows=5 width=0)
         Index Cond: (special_features @> '{"Behind The Scenes"}'::text[])
(4 rows)

Indeks GIN jest w stanie obsłużyć dopasowanie indywidualnej wartości do zindeksowanej wartości złożonej, co skutkuje planem zapytania o mniej niż połowę kosztu oryginału.

Wyeliminuj zduplikowane indeksy

Z biegiem czasu indeksy gromadzą się, a czasami dodawany jest jeden, który ma dokładnie taką samą definicję jak inny. Możesz użyć widoku katalogu pg_indexes aby uzyskać czytelne dla człowieka definicje indeksów SQL. Możesz także łatwo wykryć identyczne definicje:

  SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
    FROM pg_indexes
GROUP BY defn
  HAVING count(*) > 1;

A oto wynik po uruchomieniu na giełdowej bazie danych pagila:

pagila=#   SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
pagila-#     FROM pg_indexes
pagila-# GROUP BY defn
pagila-#   HAVING count(*) > 1;
                                indexes                                 |                                defn
------------------------------------------------------------------------+------------------------------------------------------------------
 {payment_p2017_01_customer_id_idx,idx_fk_payment_p2017_01_customer_id} | CREATE INDEX  ON public.payment_p2017_01 USING btree (customer_id
 {payment_p2017_02_customer_id_idx,idx_fk_payment_p2017_02_customer_id} | CREATE INDEX  ON public.payment_p2017_02 USING btree (customer_id
 {payment_p2017_03_customer_id_idx,idx_fk_payment_p2017_03_customer_id} | CREATE INDEX  ON public.payment_p2017_03 USING btree (customer_id
 {idx_fk_payment_p2017_04_customer_id,payment_p2017_04_customer_id_idx} | CREATE INDEX  ON public.payment_p2017_04 USING btree (customer_id
 {payment_p2017_05_customer_id_idx,idx_fk_payment_p2017_05_customer_id} | CREATE INDEX  ON public.payment_p2017_05 USING btree (customer_id
 {idx_fk_payment_p2017_06_customer_id,payment_p2017_06_customer_id_idx} | CREATE INDEX  ON public.payment_p2017_06 USING btree (customer_id
(6 rows)

Indeksy superzestawów

Możliwe jest również, że skończysz z wieloma indeksami, w których jeden indeksuje zestaw kolumn, który robi drugi. Może to być lub nie być pożądane — ten nadzbiór może skutkować skanowaniem tylko indeksu, co jest dobrą rzeczą, ale może zajmować zbyt dużo miejsca lub może zapytanie, które pierwotnie miało zoptymalizować, nie jest już używane.

Jeśli chcesz zautomatyzować wykrywanie takich indeksów, pg_catalog tablepg_index jest dobrym punktem wyjścia.

Nieużywane indeksy

W miarę ewolucji aplikacji korzystających z bazy danych zmieniają się zapytania, z których korzystają. Indeksy dodane wcześniej nie mogą być już używane przez żadne zapytanie. Za każdym razem, gdy indeks jest skanowany, jest on odnotowywany przez menedżera statystyk, a skumulowana liczba jest dostępna w widoku katalogu systemowego pg_stat_user_indexes jako wartość idx_scan . Monitorowanie tej wartości przez pewien okres (powiedzmy miesiąc) daje dobre wyobrażenie o tym, które indeksy są nieużywane i można je usunąć.

Oto zapytanie, aby uzyskać aktualną liczbę skanów dla wszystkich indeksów w „schemacie publicznym”:

SELECT relname, indexrelname, idx_scan
FROM   pg_catalog.pg_stat_user_indexes
WHERE  schemaname = 'public';

z takim wyjściem:

pagila=# SELECT relname, indexrelname, idx_scan
pagila-# FROM   pg_catalog.pg_stat_user_indexes
pagila-# WHERE  schemaname = 'public'
pagila-# LIMIT  10;
    relname    |    indexrelname    | idx_scan
---------------+--------------------+----------
 customer      | customer_pkey      |    32093
 actor         | actor_pkey         |     5462
 address       | address_pkey       |      660
 category      | category_pkey      |     1000
 city          | city_pkey          |      609
 country       | country_pkey       |      604
 film_actor    | film_actor_pkey    |        0
 film_category | film_category_pkey |        0
 film          | film_pkey          |    11043
 inventory     | inventory_pkey     |    16048
(10 rows)

Odbuduj indeksy z mniejszymi blokowaniami

Często zdarza się, że indeksy muszą zostać odtworzone. Indeksy mogą również stać się rozdęte, a odtworzenie indeksu może to naprawić, powodując szybsze skanowanie. Indeksy mogą również ulec uszkodzeniu. Zmiana parametrów indeksu może również wymagać odtworzenia indeksu.

Włącz tworzenie indeksu równoległego

W PostgreSQL 11 tworzenie indeksów B-Tree jest współbieżne. Może korzystać z wielu równoległych procesów roboczych, aby przyspieszyć tworzenie indeksu. Musisz jednak upewnić się, że te wpisy konfiguracyjne są odpowiednio ustawione:

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

Wartości domyślne są nadmiernie małe. W idealnym przypadku liczby te powinny wzrosnąć wraz z liczbą rdzeni procesora. Więcej informacji znajdziesz w dokumentacji.

Tworzenie indeksów w tle

Możesz także utworzyć indeks w tle, używając WSPÓLNIE parametr CREATE INDEX polecenie:

pagila=# CREATE INDEX CONCURRENTLY idx_address1 ON address(district);
CREATE INDEX

Różni się to od wykonywania zwykłego indeksu tworzenia, ponieważ nie wymaga blokady tabeli, a zatem nie blokuje zapisów. Z drugiej strony, ukończenie zajmuje więcej czasu i zasobów.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak zaimportować bazę danych PostgreSQL za pomocą phpPgAdmin

  2. Indeks PostgreSQL nie jest używany do zapytań o zakresy IP

  3. Przesyłaj dane między bazami danych za pomocą PostgreSQL

  4. Django cache.set() powodujący błąd zduplikowanego klucza

  5. Django-DB-Migration:nie można ALTER TABLE, ponieważ ma oczekujące zdarzenia wyzwalające