Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ всС возмоТности индСксов Π² PostgreSQL

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ всС возмоТности индСксов Π² PostgreSQL
Π’ ΠΌΠΈΡ€Π΅ Postgres индСксы ΠΊΡ€Π°ΠΉΠ½Π΅ Π²Π°ΠΆΠ½Ρ‹ для эффСктивной Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ ΠΏΠΎ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Ρƒ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… (Π΅Π³ΠΎ Π½Π°Π·Ρ‹Π²Π°ΡŽΡ‚ Β«ΠΊΡƒΡ‡Π°Β», heap). Postgres Π½Π΅ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ для Π½Π΅Π³ΠΎ ΠΊΠ»Π°ΡΡ‚Π΅Ρ€ΠΈΠ·Π°Ρ†ΠΈΡŽ, ΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π° MVCC ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ Ρ‚ΠΎΠΌΡƒ, Ρ‡Ρ‚ΠΎ Ρƒ вас накапливаСтся ΠΌΠ½ΠΎΠ³ΠΎ вСрсий ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΈ Ρ‚ΠΎΠ³ΠΎ ΠΆΠ΅ ΠΊΠΎΡ€Ρ‚Π΅ΠΆΠ°. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ ΠΎΡ‡Π΅Π½ΡŒ Π²Π°ΠΆΠ½ΠΎ ΡƒΠΌΠ΅Ρ‚ΡŒ ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ ΠΈ ΡΠΎΠΏΡ€ΠΎΠ²ΠΎΠΆΠ΄Π°Ρ‚ΡŒ эффСктивныС индСксы для ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΈ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ.

ΠŸΡ€Π΅Π΄Π»Π°Π³Π°ΡŽ Π²Π°ΡˆΠ΅ΠΌΡƒ вниманию нСсколько совСтов ΠΏΠΎ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΈ ΡƒΠ»ΡƒΡ‡ΡˆΠ΅Π½ΠΈΡŽ использования индСксов.

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: ΠΏΠΎΠΊΠ°Π·Π°Π½Π½Ρ‹Π΅ Π½ΠΈΠΆΠ΅ запросы Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ Π½Π° Π½Π΅ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠΌ ΠΎΠ±Ρ€Π°Π·Ρ†Π΅ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… pagila.

ИспользованиС ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰ΠΈΡ… индСксов (Covering Indexes)

Π”Π°Π²Π°ΠΉΡ‚Π΅ рассмотрим запрос Π½Π° ΠΈΠ·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ адрСсов элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹ для Π½Π΅Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ. Π’ Ρ‚Π°Π±Π»ΠΈΡ†Π΅ customer Π΅ΡΡ‚ΡŒ ΠΊΠΎΠ»ΠΎΠ½ΠΊΠ° active, ΠΈ запрос получаСтся нСслоТным:

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)

Π’ запросС вызываСтся полная ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ сканирования Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ customer. Π”Π°Π²Π°ΠΉΡ‚Π΅ создадим индСкс для столбца active:

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)

Помогло, ΠΏΠΎΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅Π΅ сканированиС ΠΏΡ€Π΅Π²Ρ€Π°Ρ‚ΠΈΠ»ΠΎΡΡŒ Π² «index scan«. Π­Ρ‚ΠΎ ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚, Ρ‡Ρ‚ΠΎ Postgres просканируСт индСкс «idx_cust1«, Π° Π·Π°Ρ‚Π΅ΠΌ дальшС ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ поиск ΠΏΠΎ ΠΊΡƒΡ‡Π΅ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΡ€ΠΎΡ‡Π΅ΡΡ‚ΡŒ значСния Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ (Π² Π΄Π°Π½Π½ΠΎΠΌ случаС, ΠΊΠΎΠ»ΠΎΠ½ΠΊΡƒ email), ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΡƒΠΆΠ½Ρ‹ запросу.

Π’ PostgreSQL 11 появились ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰ΠΈΠ΅ индСксы. Они ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ Π²ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π² сам индСкс ΠΎΠ΄Π½Ρƒ ΠΈΠ»ΠΈ нСсколько Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ β€” ΠΈΡ… значСния хранятся Π² Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅ Π΄Π°Π½Π½Ρ‹Ρ… индСкса.

Если Π±Ρ‹ ΠΌΡ‹ использовали эту Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹ Π²Π½ΡƒΡ‚Ρ€ΡŒ индСкса, Ρ‚ΠΎ Postgres’у Π½Π΅ понадобится ΠΈΡΠΊΠ°Ρ‚ΡŒ Π² ΠΊΡƒΡ‡Π΅ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ email. ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ, Π±ΡƒΠ΄Π΅Ρ‚ Π»ΠΈ это Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ:

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)

«Index Only Scan» Π³ΠΎΠ²ΠΎΡ€ΠΈΡ‚ Π½Π°ΠΌ, Ρ‡Ρ‚ΠΎ запросу Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ достаточно ΠΎΠ΄Π½ΠΎΠ³ΠΎ лишь индСкса, Ρ‡Ρ‚ΠΎ ΠΏΠΎΠΌΠΎΠ³Π°Π΅Ρ‚ ΠΈΠ·Π±Π΅Π³Π°Ρ‚ΡŒ всСх дисковых ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ Π²Π²ΠΎΠ΄Π°/Π²Ρ‹Π²ΠΎΠ΄Π° для чтСния ΠΊΡƒΡ‡ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹.

БСгодня ΠΏΠΎΠΊΡ€Ρ‹Π²Π°ΡŽΡ‰ΠΈΠ΅ индСксы доступны Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для B-Π΄Π΅Ρ€Π΅Π²ΡŒΠ΅Π². Однако Π² этом случаС усилия ΠΏΠΎ ΡΠΎΠΏΡ€ΠΎΠ²ΠΎΠΆΠ΄Π΅Π½ΠΈΡŽ Π±ΡƒΠ΄ΡƒΡ‚ Π²Ρ‹ΡˆΠ΅.

ИспользованиС частичных индСксов

ЧастичныС индСксы ΠΈΠ½Π΄Π΅ΠΊΡΠΈΡ€ΡƒΡŽΡ‚ лишь подмноТСство строк Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹. Π­Ρ‚ΠΎ позволяСт ΡΠΊΠΎΠ½ΠΎΠΌΠΈΡ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ€ индСксов ΠΈ быстрСС Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ сканированиС.

Допустим, Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список адрСсов элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹ Π½Π°ΡˆΠΈΡ… ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ² ΠΈΠ· ΠšΠ°Π»ΠΈΡ„ΠΎΡ€Π½ΠΈΠΈ. Запрос Π±ΡƒΠ΄Π΅Ρ‚ Ρ‚Π°ΠΊΠΈΠΌ:

SELECT c.email FROM customer c
JOIN address a ON c.address_id = a.address_id
WHERE a.district = 'California';
which has a query plan that involves scanning both the tables that are joined:
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)

Π§Ρ‚ΠΎ Π½Π°ΠΌ Π΄Π°Π΄ΡƒΡ‚ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹Π΅ индСксы:

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)

Π‘ΠΊΠ°Π½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ address Π±Ρ‹Π»ΠΎ Π·Π°ΠΌΠ΅Π½Π΅Π½ΠΎ сканированиСм индСкса idx_address1, Π° Π·Π°Ρ‚Π΅ΠΌ просканирована ΠΊΡƒΡ‡Π° address.

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ это частый запрос ΠΈ Π΅Π³ΠΎ Π½ΡƒΠΆΠ½ΠΎ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ, ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ частичный индСкс, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ индСксируСт лишь Ρ‚Π΅ строки с адрСсами, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Ρ€Π°ΠΉΠΎΠ½ β€˜California’:

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)

Π’Π΅ΠΏΠ΅Ρ€ΡŒ запрос считываСт Ρ‚ΠΎΠ»ΡŒΠΊΠΎ idx_address2 ΠΈ Π½Π΅ Ρ‚Ρ€ΠΎΠ³Π°Π΅Ρ‚ Ρ‚Π°Π±Π»ΠΈΡ†Ρƒ address.

ИспользованиС ΠΌΠ½ΠΎΠ³ΠΎΠ·Π½Π°Ρ‡Π½Ρ‹Ρ… индСксов (Multi-Value Indexes)

НСкоторыС ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΡƒΠΆΠ½ΠΎ ΠΏΡ€ΠΎΠΈΠ½Π΄Π΅ΠΊΡΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ, ΠΌΠΎΠ³ΡƒΡ‚ Π½Π΅ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ скалярного Ρ‚ΠΈΠΏΠ° Π΄Π°Π½Π½Ρ‹Ρ…. Π’ΠΈΠΏΡ‹ ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ Π½Π°ΠΏΠΎΠ΄ΠΎΠ±ΠΈΠ΅ jsonb, arrays ΠΈ tsvector ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ составныС ΠΈΠ»ΠΈ мноТСствСнныС значСния. Если Π²Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΈΠ½Π΄Π΅ΠΊΡΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚Π°ΠΊΠΈΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΊΠΈ, Ρ‚ΠΎ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ приходится ΠΈΡΠΊΠ°Ρ‚ΡŒ ΠΏΠΎ всСм ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌ значСниям Π² этих ΠΊΠΎΠ»ΠΎΠ½ΠΊΠ°Ρ….

ΠŸΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π½Π°ΠΉΡ‚ΠΈ названия всСх Ρ„ΠΈΠ»ΡŒΠΌΠΎΠ², содСрТащиС Π½Π°Ρ€Π΅Π·ΠΊΠΈ ΠΈΠ· Π½Π΅ΡƒΠ΄Π°Ρ‡Π½Ρ‹Ρ… Π΄ΡƒΠ±Π»Π΅ΠΉ. Π’ Ρ‚Π°Π±Π»ΠΈΡ†Π΅ film Π΅ΡΡ‚ΡŒ тСкстовая ΠΊΠΎΠ»ΠΎΠ½ΠΊΠ°, Π½Π°Π·Ρ‹Π²Π°ΡŽΡ‰Π°ΡΡΡ special_features. Если Ρƒ Ρ„ΠΈΠ»ΡŒΠΌΠ° Π΅ΡΡ‚ΡŒ это «особоС свойство», Ρ‚ΠΎ Π² ΠΊΠΎΠ»ΠΎΠ½ΠΊΠ΅ содСрТится элСмСнт Π² Π²ΠΈΠ΄Π΅ тСкстового массива Behind The Scenes. Для поиска всСх Ρ‚Π°ΠΊΠΈΡ… Ρ„ΠΈΠ»ΡŒΠΌΠΎΠ² Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ всС ряды с Β«Behind The ScenesΒ» ΠΏΡ€ΠΈ Π»ΡŽΠ±Ρ‹Ρ… значСниях массива special_features:

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

ΠžΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€ влоТСнности (containment operator) @> провСряСт, являСтся Π»ΠΈ правая Ρ‡Π°ΡΡ‚ΡŒ подмноТСством Π»Π΅Π²ΠΎΠΉ части.

План запроса:

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)

ΠšΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅Ρ‚ ΠΏΠΎΠ»Π½ΠΎΠ΅ сканированиС ΠΊΡƒΡ‡ΠΈ со ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒΡŽ 67.

ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈΠΌ, ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π»ΠΈ Π½Π°ΠΌ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΉ индСкс B-Π΄Π΅Ρ€Π΅Π²Π°:

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)

ИндСкс Π΄Π°ΠΆΠ΅ Π½Π΅ рассматривался. ИндСкс B-Π΄Π΅Ρ€Π΅Π²Π° Π½Π΅ догадываСтся ΠΎ сущСствовании ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… элСмСнтов Π² индСксируСмых значСниях.

Нам Π½ΡƒΠΆΠ΅Π½ 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)

GIN-индСкс ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ сопоставлСниС ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ с проиндСксированными составными значСниями, Π² Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅ ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΠ»Π°Π½Π° запроса ΡƒΠΌΠ΅Π½ΡŒΡˆΠΈΡ‚ΡΡ Π±ΠΎΠ»Π΅Π΅ Ρ‡Π΅ΠΌ Π²Π΄Π²ΠΎΠ΅.

ИзбавляСмся ΠΎΡ‚ дублирования индСксов

Π˜Π½Π΄Π΅ΠΊΡΡ‹ Π½Π°ΠΊΠ°ΠΏΠ»ΠΈΠ²Π°ΡŽΡ‚ΡΡ со Π²Ρ€Π΅ΠΌΠ΅Π½Π΅ΠΌ, ΠΈ ΠΈΠ½ΠΎΠ³Π΄Π° Π½ΠΎΠ²Ρ‹ΠΉ индСкс ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Ρ‚ΠΎ ΠΆΠ΅ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ ΠΈ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΡ…. Для получСния ΡƒΠ΄ΠΎΠ±Π½Ρ‹Ρ… для чтСния Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠΎΠΌ SQL-ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠΉ индСксов ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΠ°Ρ‚Π°Π»ΠΎΠΆΠ½ΠΎΠ΅ прСдставлСниС pg_indexes. Π’Ρ‹ Ρ‚Π°ΠΊΠΆΠ΅ смоТСтС Π»Π΅Π³ΠΊΠΎ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ опрСдСлСния:

 SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
    FROM pg_indexes
GROUP BY defn
  HAVING count(*) > 1;
And here’s the result when run on the stock pagila database:
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)

Π˜Π½Π΄Π΅ΠΊΡΡ‹ надмноТСств (Superset Indexes)

ΠœΠΎΠΆΠ΅Ρ‚ ΡΠ»ΡƒΡ‡ΠΈΡ‚ΡŒΡΡ Ρ‚Π°ΠΊ, Ρ‡Ρ‚ΠΎ Ρƒ вас накопится ΠΌΠ½ΠΎΠ³ΠΎ индСксов, ΠΎΠ΄ΠΈΠ½ ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… индСксируСт надмноТСство ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΈΠ½Π΄Π΅ΠΊΡΠΈΡ€ΡƒΡŽΡ‚ Π΄Ρ€ΡƒΠ³ΠΈΠ΅ индСксы. Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΊΠ°ΠΊ ΠΆΠ΅Π»Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, Ρ‚Π°ΠΊ ΠΈ Π½Π΅Ρ‚ β€” надмноТСство ΠΌΠΎΠΆΠ΅Ρ‚ привСсти ΠΊ ΡΠΊΠ°Π½ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡŽ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ индСксам, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ€ΠΎΡˆΠΎ, Π½ΠΎ ΠΏΡ€ΠΈ этом ΠΎΠ½ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π½ΠΈΠΌΠ°Ρ‚ΡŒ слишком ΠΌΠ½ΠΎΠ³ΠΎ мСста, ΠΈΠ»ΠΈ запрос, для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π°Π»ΠΎΡΡŒ это надмноТСство, ΡƒΠΆΠ΅ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ.

Если Π²Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ Ρ‚Π°ΠΊΠΈΡ… индСксов, Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Ρ‡Π°Ρ‚ΡŒ с pg_index ΠΈΠ· Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ pg_catalog.

ΠΠ΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ индСксы

По ΠΌΠ΅Ρ€Π΅ развития ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ…, Ρ€Π°Π·Π²ΠΈΠ²Π°ΡŽΡ‚ΡΡ ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅ ΠΈΠΌΠΈ запросы. Π”ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Π΅ Ρ€Π°Π½Π΅Π΅ индСксы ΠΌΠΎΠ³ΡƒΡ‚ ΡƒΠΆΠ΅ Π½Π΅ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒΡΡ Π½ΠΈ ΠΎΠ΄Π½ΠΈΠΌ запросом. ΠŸΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ сканировании индСкса ΠΎΠ½ отмСчаСтся диспСтчСром статистики, ΠΈ Π² прСдставлСнии систСмного ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³Π° pg_stat_user_indexes ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ idx_scan, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ являСтся Π½Π°ΠΊΠΎΠΏΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ счётчиком. ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ этого Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π·Π° ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΠΊ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ (скаТСм, мСсяц) даст Ρ…ΠΎΡ€ΠΎΡˆΠ΅Π΅ прСдставлСниС ΠΎ Ρ‚ΠΎΠΌ, ΠΊΠ°ΠΊΠΈΠ΅ индСксы Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ ΠΈ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹.

Π’ΠΎΡ‚ запрос Π½Π° ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΡ… счётчиков сканирования всСх индСксов Π² схСмС β€˜public’:

SELECT relname, indexrelname, idx_scan
FROM   pg_catalog.pg_stat_user_indexes
WHERE  schemaname = 'public';
with output like this:
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)

ΠŸΠ΅Ρ€Π΅ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ индСксов с мСньшим количСством Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ

Π§Π°ΡΡ‚Π΅Π½ΡŒΠΊΠΎ индСксы приходится ΠΏΠ΅Ρ€Π΅ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΠΊΠΎΠ³Π΄Π° ΠΎΠ½ΠΈ Ρ€Π°Π·Π΄ΡƒΠ²Π°ΡŽΡ‚ΡΡ Π² Ρ€Π°Π·ΠΌΠ΅Ρ€Π°Ρ…, ΠΈ пСрСсозданиС ΠΌΠΎΠΆΠ΅Ρ‚ ΡƒΡΠΊΠΎΡ€ΠΈΡ‚ΡŒ сканированиС. Π’Π°ΠΊΠΆΠ΅ индСксы ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠΎΠ²Ρ€Π΅ΠΆΠ΄Π°Ρ‚ΡŒΡΡ. ИзмСнСниС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² индСкса Ρ‚ΠΎΠΆΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΠΎΡ‚Ρ€Π΅Π±ΠΎΠ²Π°Ρ‚ΡŒ Π΅Π³ΠΎ пСрСсоздания.

Π’ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ ΠΏΠ°Ρ€Π°Π»Π»Π΅Π»ΡŒΠ½ΠΎΠ΅ созданиС индСксов

Π’ PostgreSQL 11 созданиС индСкса B-Tree являСтся ΠΊΠΎΠ½ΠΊΡƒΡ€Π΅Π½Ρ‚Π½Ρ‹ΠΌ. Для ускорСния процСсса создания ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ нСсколько ΠΏΠ°Ρ€Π°Π»Π»Π΅Π»ΡŒΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΡ… Π²ΠΎΡ€ΠΊΠ΅Ρ€ΠΎΠ². Однако ΡƒΠ±Π΅Π΄ΠΈΡ‚Π΅ΡΡŒ, Ρ‡Ρ‚ΠΎ эти ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Π·Π°Π΄Π°Π½Ρ‹ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ:

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

ЗначСния ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ слишком ΠΌΠ°Π»Ρ‹. Π’ ΠΈΠ΄Π΅Π°Π»Π΅, эти числа Π½ΡƒΠΆΠ½ΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Ρ‚ΡŒ вмСстС с количСством ядСр процСссора. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅ Ρ‡ΠΈΡ‚Π°ΠΉΡ‚Π΅ Π² Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ.

ЀоновоС созданиС индСксов

Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ индСкс Π² Ρ„ΠΎΠ½ΠΎΠ²ΠΎΠΌ Ρ€Π΅ΠΆΠΈΠΌΠ΅, воспользовавшись ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ CONCURRENTLY ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ CREATE INDEX:

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

Π­Ρ‚Π° ΠΏΡ€ΠΎΡ†Π΅Π΄ΡƒΡ€Π° создания индСкса отличаСтся ΠΎΡ‚ ΠΎΠ±Ρ‹Ρ‡Π½ΠΎΠΉ Ρ‚Π΅ΠΌ, Ρ‡Ρ‚ΠΎ ΠΎΠ½Π° Π½Π΅ Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ блокирования Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹, Π° Π·Π½Π°Ρ‡ΠΈΡ‚ ΠΈ Π½Π΅ Π±Π»ΠΎΠΊΠΈΡ€ΡƒΠ΅Ρ‚ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ записи. Π‘ Π΄Ρ€ΡƒΠ³ΠΎΠΉ стороны, ΠΎΠ½Π° Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ большС Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΈ потрСбляСт большС рСсурсов.

Postgres прСдоставляСт мноТСство Π³ΠΈΠ±ΠΊΠΈΡ… возмоТностСй для создания индСксов ΠΈ ΠΏΡƒΡ‚Π΅ΠΉ Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ Π»ΡŽΠ±Ρ‹Ρ… частных случаСв, Π° Ρ‚Π°ΠΊ ΠΆΠ΅ прСдоставляСт способы управлСния Π±Π°Π·ΠΎΠΉ Π΄Π°Π½Π½Ρ‹Ρ… Π½Π° случай Π²Π·Ρ€Ρ‹Π²Π½ΠΎΠ³ΠΎ роста вашСго прилоТСния. НадССмся, Ρ‡Ρ‚ΠΎ эти совСты ΠΏΠΎΠΌΠΎΠ³ΡƒΡ‚ Π²Π°ΠΌ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ запросы быстрыми, Π° Π±Π°Π·Ρƒ Π³ΠΎΡ‚ΠΎΠ²ΠΎΠΉ ΠΌΠ°ΡΡˆΡ‚Π°Π±ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ.

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com

Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ