sql >> Databáze >  >> RDS >> PostgreSQL

Získejte maximum ze svých indexů PostgreSQL

Ve světě Postgres jsou indexy nezbytné pro efektivní navigaci v úložišti tabulkových dat (neboli „hromadě“). Postgres neudržuje klastrování pro heap a architektura MVCC vede k více verzím stejného tuplelyingu. Vytváření a udržování účinných a efektivních indexů pro podporu aplikací je základní dovedností.

Čtěte dále a podívejte se na několik tipů pro optimalizaci a zlepšení používání indexů ve vašem nasazení.

Poznámka:Dotazy zobrazené níže se spouštějí v nemodifikované vzorové databázi.

Použití krycích indexů

Zvažte dotaz k načtení e-mailů všech neaktivních zákazníků. Zákazník tabulka má aktivní a dotaz je přímočarý:

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)

Dotaz vyžaduje úplné sekvenční prohledání tabulky zákazníků. Pojďme vytvořit index na aktivním sloupci:

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 pomáhá a sekvenční skenování se stalo „indexovým skenováním“. To znamená, že Postgres naskenuje index „idx_cust1“ a poté dále vyhledá v hromadě tabulky, aby načetl hodnoty ostatních sloupců (v tomto případě e-mail sloupec), který dotaz potřebuje.

PostgreSQL 11 zavedl krycí indexy. Tato funkce vám umožňuje zahrnout jeden nebo více dalších sloupců do samotného indexu – to znamená, že hodnoty těchto dalších sloupců jsou uloženy v úložišti dat indexu.

Pokud bychom použili tuto funkci a zahrnuli hodnotu e-mailu do indexu, Postgres se nebude muset dívat do hromady tabulky, aby získal hodnotuemail . Uvidíme, jestli to funguje:

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)

„Prohledávání pouze indexu“ nám říká, že dotaz je nyní zcela uspokojen samotným indexem, čímž se potenciálně vyhneme všem I/O disku pro čtení haldy tabulky.

Krycí indexy jsou zatím dostupné pouze pro indexy B-Stromu. Také náklady na udržování indexu krytí jsou přirozeně vyšší než u běžného indexu.

Použít částečné indexy

Částečné indexy indexují pouze podmnožinu řádků v tabulce. Díky tomu jsou indexy menší a jejich procházení je rychlejší.

Předpokládejme, že potřebujeme získat seznam e-mailů zákazníků v Kalifornii. Dotaz je:

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

který má plán dotazů, který zahrnuje skenování obou tabulek, které jsou spojeny:

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)

Podívejme se, co nám přinese běžný index:

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)

Skenování adresy byla nahrazena indexovým skenováním přes idx_address1 a skenování hromady adres.

Za předpokladu, že se jedná o častý dotaz a je třeba jej optimalizovat, můžeme použít samostatný index, který indexuje pouze ty řádky adres, kde je okres ‚Kalifornie‘:

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)

Dotaz nyní čte pouze index idx_address2 a nedotýká seadresy stolu .

Použití indexů s více hodnotami

Některé sloupce, které vyžadují indexování, nemusí mít skalární datový typ. Typy sloupců jako jsonb , pole a tsvector mají složené nebo vícenásobné hodnoty. Potřebujete-li takové sloupce indexovat, obvykle se stává, že potřebujete prohledat i jednotlivé hodnoty v těchto sloupcích.

Pokusme se najít všechny filmové tituly, které obsahují zákulisní výstupy. film tabulka má sloupec textového pole s názvem special_features , který obsahuje prvek textového pole Behind The Scenes pokud film má tuto vlastnost. Abychom našli všechny takové filmy, musíme vybrat všechny řádky, které vjakémkoli obsahují „Behind The Scenes“ z hodnot pole special_features :

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

Operátor kontejnmentu @> zkontroluje, zda je levá strana nadmnožinou pravé strany.

Zde je plán dotazů:

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ž vyžaduje úplné skenování haldy za cenu 67.

Podívejme se, zda pomáhá pravidelný index B-stromu:

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)

S indexem se ani nepočítá. Index B-Tree netuší, že v hodnotě, kterou indexoval, jsou jednotlivé prvky.

Co potřebujeme, je index 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)

Index GIN je schopen podporovat shodu individuální hodnoty s indexovanou složenou hodnotou, což vede k plánu dotazů s méně než polovičními náklady oproti originálu.

Odstranění duplicitních indexů

Postupem času se indexy hromadí a někdy se přidá jeden, který má přesně stejnou definici jako jiný. Můžete použít katalogové zobrazení pg_indexes získat pro člověka čitelné SQL definice indexů. Můžete také snadno zjistit identické definice:

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

A zde je výsledek při spuštění v databázi 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)

Indexy supermnožiny

Je také možné, že skončíte s více indexy, kde jeden indexuje jako superset sloupců, který dělá druhý. To může, ale nemusí být žádoucí – supermnožina může mít za následek skenování pouze na základě indexu, což je dobrá věc, ale může zabírat příliš mnoho místa nebo možná dotaz, který byl původně určen k optimalizaci, již není používán.

Pokud chcete automatizovat detekci takových indexů, pg_catalog tablepg_index je dobrým výchozím bodem.

Nepoužité indexy

Jak se vyvíjejí aplikace využívající databázi, vyvíjejí se i dotazy, které používají. Indexy, které byly přidány dříve, již nelze používat v žádném dotazu. Pokaždé, když je index naskenován, je zaznamenán správcem statistik a v zobrazení systémového katalogu pg_stat_user_indexes je k dispozici kumulativní počet jako hodnotu idx_scan . Sledování této hodnoty po určitou dobu (řekněme měsíc) poskytuje dobrou představu o tom, které indexy jsou nepoužívané a lze je odstranit.

Zde je dotaz na získání aktuálních počtů skenování pro všechny indexy ve schématu ‚public‘:

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

s výstupem takto:

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)

Znovu sestavení indexů s menším zamykáním

Není neobvyklé, že je nutné indexy znovu vytvořit. Indexy se také mohou nafouknout a opětovné vytvoření indexu to může opravit, což způsobí, že bude rychlejší skenování. Indexy se také mohou poškodit. Změna parametrů indexu může také vyžadovat opětovné vytvoření indexu.

Povolit vytváření paralelního indexu

V PostgreSQL 11 je vytváření indexu B-Stromu souběžné. Pro urychlení vytváření indexu může využívat více paralelních pracovníků. Musíte se však ujistit, že tyto položky konfigurace jsou správně nastaveny:

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

Výchozí hodnoty jsou nepřiměřeně malé. V ideálním případě by se tato čísla měla zvyšovat s počtem jader CPU. Další informace naleznete v dokumentaci.

Vytváření indexů na pozadí

Můžete také vytvořit index na pozadí pomocí SOUČASNĚ parametr CREATE INDEX příkaz:

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

To se liší od běžného vytváření indexu v tom, že nevyžaduje zámek nad tabulkou, a proto nezamyká zápisy. Nevýhodou je, že dokončení vyžaduje více času a zdrojů.


  1. JSON_TYPE() – Získejte typ hodnoty JSON v MySQL

  2. Překlenutí mezery v Azure:Spravované instance

  3. Oracle:Vyberte z datového typu záznamu

  4. Hledejte shodu celého slova v MySQL