PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Извличане на максимума от вашите PostgreSQL индекси

В света на Postgres индексите са от съществено значение за ефективно навигиране в съхранението на таблични данни (известно още като „хийп“). Postgres не поддържа клъстериране за theheap и MVCC архитектурата води до множество версии на едно и също кортежи наоколо. Създаването и поддържането на ефективни и ефикасни индекси за поддръжка на приложения е основно умение.

Прочетете, за да разгледате няколко съвета за оптимизиране и подобряване на използването на индекси във вашето внедряване.

Забележка:Показаните по-долу заявки се изпълняват в немодифицирана примерна база данни на pagila.

Използвайте индекси за покриване

Помислете за заявка за извличане на имейлите на всички неактивни клиенти. Клиентът таблицата има активен колона и заявката е ясна:

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)

Заявката изисква пълно последователно сканиране на таблицата с клиенти. Нека създадем индекс на активната колона:

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)

Това помага и последователното сканиране се превърна в „индексно сканиране“. Това означава, че Postgres ще сканира индекса „idx_cust1“ и след това ще потърси допълнително таблицата, за да прочете другите стойности на колоните (в този случай имейл колона), от които заявката се нуждае.

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)

„Сканиране само на индекса“ ни казва, че заявката вече е напълно удовлетворена от самия индекс, като по този начин потенциално се избягва целият I/O диск за четене на купчината на таблицата.

Покриващите индекси са налични само за B-Tree индекси към момента. Освен това разходите за поддържане на индекс на покритие са естествено по-високи от обикновените.

Използвайте частични индекси

Частичните индекси индексират само подмножество от редовете в таблица. Това прави индексите по-малки по размер и по-бързо за сканиране.

Да приемем, че трябва да получим списъка с имейли на клиенти, намиращи се в Калифорния. Запитването е:

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

който има план за заявка, който включва сканиране и на двете таблици, които са обединени:

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)

Сканирането на адрес е заменен с индексно сканиране през idx_address1 , и сканиране на купчината от адреси.

Ако приемем, че това е честа заявка и трябва да бъде оптимизирана, можем да използваме разделен индекс, който индексира само онези редове с адрес, където областта е „Калифорния“:

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 и не докосва табладреса .

Използвайте индекси с няколко стойности

Някои колони, които се нуждаят от индексиране, може да нямат скаларен тип данни. Типове колони като jsonb , масиви и цвектор имат съставни или множествени стойности. Ако трябва да индексирате такива колони, обикновено се налага да търсите и в отделните стойности в тези колони.

Нека се опитаме да намерим всички заглавия на филми, които включват задкулисни кадри. Филмът таблицата има колона от текстов масив, наречена special_features , който включва елемента от текстов масив Зад кулисите ако филмът има тази функция. За да намерим всички такива филми, трябва да изберем всички редове, които имат „Зад кулисите“ вкойто и да е от стойностите на масива special_features :

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

Операторът за ограничаване @> проверява дали лявата страна е надмножество от дясната страна.

Ето плана на заявката:

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-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)

Индексът дори не се взема предвид. Индексът B-Tree няма представа, че в индексираната от него стойност има отделни елементи.

Това, от което се нуждаем, е 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 е в състояние да поддържа съвпадение на индивидуалната стойност спрямо индексираната съставна стойност, което води до план за заявка с по-малко от половината от цената на оригинала.

Елиминиране на дублиращи се индекси

С течение на времето индексите се натрупват и понякога се добавя един, който има точно същата дефиниция като друг. Можете да използвате каталожния изглед pg_indexes за да получите разбираемите от човека SQL дефиниции на индекси. Можете също така лесно да откриете идентични дефиниции:

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

И ето резултата, когато се изпълнява в стандартната база данни 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)

Superset Indexes

Възможно е също така да се окажете с множество индекси, където единият индексира като супернабор от колони, които прави другият. Това може да е желателно или не – супернаборът може да доведе до сканиране само с индекс, което е добро нещо, но може да заеме твърде много място или може би заявката, която първоначално е била предназначена да оптимизира, вече не се използва.

Ако искате да автоматизирате откриването на такива индекси, pg_catalog tablepg_index е добра отправна точка.

Неизползвани индекси

С развитието на приложенията, които използват базата данни, се развиват и заявките, които използват. Индексите, които бяха добавени по-рано, може вече да не се използват от никаква заявка. Всеки път, когато индексът се сканира, той се отбелязва от мениджъра на статистиките и натрупването на броя е налично в изгледа на системния каталог pg_stat_user_indexes като стойност idx_scan . Наблюдението на тази стойност за определен период от време (да речем, месец) дава добра представа кои индекси са неизползвани и могат да бъдат премахнати.

Ето заявката за получаване на текущия брой сканирания за всички индекси в „public“schema:

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

с изход като този:

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)

Повторно изграждане на индекси с по-малко заключване

Не е необичайно, че индексите трябва да бъдат пресъздадени. Индексите също могат да се раздуят и повторното създаване на индекса може да поправи това, което води до по-бързо сканиране. Индексите също могат да се повредят. Промяната на параметрите на индекса също може да се нуждае от повторно създаване на индекса.

Активиране на създаването на Paralell Index

В PostgreSQL 11 създаването на индекс на B-Tree е едновременно. Може да използва множество паралелни работници, за да ускори създаването на индекса. Въпреки това, трябва да се уверите, че тези конфигурационни записи са зададени по подходящ начин:

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

Стойностите по подразбиране са неоправдано малки. В идеалния случай тези числа трябва да се увеличават с броя на ядрата на процесора. Вижте документите за повече информация.

Създаване на индекси във фонов режим

Можете също да създадете индекс във фонов режим, като използвате ЕДНОЛЕТНО параметър на CREATE INDEX команда:

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

Това е различно от правенето на обикновен индекс за създаване по това, че не изисква заключване на таблицата и следователно не блокира записите. От друга страна, отнема повече време и ресурси за завършване.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Дълбоко гмуркане на доставчик на облак:PostgreSQL на Microsoft Azure

  2. Избиране на данни в Postgres масив

  3. Свързване с Postgresql в докер контейнер отвън

  4. Нови функции за съвместимост на Oracle в PostgresPlus Advanced Server 9.3Beta

  5. Групиран LIMIT в PostgreSQL:показване на първите N реда за всяка група?