Utiliser toutes les capacités des index dans PostgreSQL

Utiliser toutes les capacités des index dans PostgreSQL
Dans le monde de Postgres, les index sont essentiels pour naviguer efficacement dans le stockage de la base de donnĂ©es (appelĂ© « heap »). Postgres ne prend pas en charge la clustering pour cela, et l'architecture MVCC fait que de nombreuses versions d'une mĂȘme ligne s'accumulent. Il est donc crucial de savoir comment crĂ©er et entretenir des index efficaces pour soutenir les applications.

Je vous propose quelques conseils pour optimiser et améliorer l'utilisation des index.

Remarque : les requĂȘtes ci-dessous fonctionnent sur un exemple de base de donnĂ©es pagila.

Utilisation des index couverts (Covering Indexes)

Prenons une requĂȘte pour extraire les adresses Ă©lectroniques des utilisateurs inactifs. Dans la table customer il y a une colonne active, et la requĂȘte reste simple :

pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                        PLAN DE REQUÊTE
-----------------------------------------------------------
 Scan séquentiel sur customer  (coût=0.00..16.49 lignes=15 largeur=32)
   Filtre : (active = 0)
(2 lignes)

La requĂȘte effectue une analyse sĂ©quentielle complĂšte de la table. customerCrĂ©ons un index pour la colonne active:

pagila=# CREATE INDEX idx_cust1 ON customer(active);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                                 PLAN DE REQUÊTE
-----------------------------------------------------------------------------
 Scan d'index utilisant idx_cust1 sur customer  (coût=0.28..12.29 lignes=15 largeur=32)
   Condition d'index : (active = 0)
(2 lignes)

Cela a aidĂ©, l'analyse suivante est devenue un «scan d'index». Cela signifie que Postgres va examiner l'index «idx_cust1», puis continuer Ă  chercher dans le heap de la table pour lire les valeurs d'autres colonnes (dans ce cas, la colonne email), nĂ©cessaires Ă  la requĂȘte.

Dans PostgreSQL 11, des index couverts ont Ă©tĂ© introduits. Ils permettent d'inclure dans l'index une ou plusieurs colonnes supplĂ©mentaires — leurs valeurs sont stockĂ©es dans le stockage des donnĂ©es de l'index.

Si nous utilisions cette fonctionnalité et ajoutions la valeur de l'e-mail à l'intérieur de l'index, Postgres n'aurait pas besoin de chercher dans le heap de la table pour obtenir la valeur. emailVoyons si cela fonctionnera :

pagila=# CREATE INDEX idx_cust2 ON customer(active) INCLUDE (email);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
                                    PLAN DE REQUÊTE
----------------------------------------------------------------------------------
 Scan seulement d'index utilisant idx_cust2 sur customer  (coût=0.28..12.29 lignes=15 largeur=32)
   Condition d'index : (active = 0)
(2 lignes)

«Scan seulement d'index» nous indique que la requĂȘte nĂ©cessite maintenant uniquement l'index, ce qui permet d'Ă©viter toutes les opĂ©rations d'entrĂ©e/sortie disque pour lire le heap de la table.

Aujourd'hui, les index couvrants ne sont disponibles que pour les arbres B. Cependant, dans ce cas, les efforts de maintenance seront plus élevés.

Utilisation des index partiels

Les index partiels n'indexent qu'un sous-ensemble des lignes d'une table. Cela permet d'économiser de l'espace pour les index et d'effectuer des scans plus rapidement.

Supposons que nous devons obtenir la liste des adresses e-mail de nos clients en Californie. La requĂȘte sera telle que :

SELECT c.email FROM customer c
JOIN address a ON c.address_id = a.address_id
WHERE a.district = 'California';
qui a un plan de requĂȘte impliquant le scan des deux tables jointes :
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
                              PLAN DE REQUÊTE
----------------------------------------------------------------------
 Jointure Hachée  (coût=15.65..32.22 lignes=9 largeur=32)
   Cond. Hachée : (c.address_id = a.address_id)
   ->  Scan Séquentiel sur customer c  (coût=0.00..14.99 lignes=599 largeur=34)
   ->  Haché  (coût=15.54..15.54 lignes=9 largeur=4)
         ->  Scan Séquentiel sur address a  (coût=0.00..15.54 lignes=9 largeur=4)
               Filtre : (district = 'California'::text)
(6 lignes)

Que nous donneront les index classiques :

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';
                                      PLAN DE REQUÊTE
---------------------------------------------------------------------------------------
 Jointure Hachée  (coût=12.98..29.55 lignes=9 largeur=32)
   Cond. Hachée : (c.address_id = a.address_id)
   ->  Scan Séquentiel sur customer c  (coût=0.00..14.99 lignes=599 largeur=34)
   ->  Haché  (coût=12.87..12.87 lignes=9 largeur=4)
         ->  Scan de Tas Bitmap sur address a  (coût=4.34..12.87 lignes=9 largeur=4)
               Condition de Vérification : (district = 'California'::text)
               ->  Scan d'Index Bitmap sur idx_address1  (coût=0.00..4.34 lignes=9 largeur=0)
                     Cond. d'Index : (district = 'California'::text)
(8 lignes)

Scan address a été remplacé par le scan de l'index idx_address1, puis une table a été scanné address.

Comme c'est une requĂȘte frĂ©quente qui nĂ©cessite une optimisation, nous pouvons utiliser un index partiel qui n'indexe que les lignes avec des adresses dans lesquelles le district ‘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';
                                           PLAN DE REQUÊTE
------------------------------------------------------------------------------------------------
 Jointure Hachée  (coût=12.38..28.96 lignes=9 largeur=32)
   Cond. Hachée : (c.address_id = a.address_id)
   ->  Scan Séquentiel sur customer c  (coût=0.00..14.99 lignes=599 largeur=34)
   ->  Haché  (coût=12.27..12.27 lignes=9 largeur=4)
         ->  Scan Index Seul utilisant idx_address2 sur address a  (coût=0.14..12.27 lignes=9 largeur=4)
(5 lignes)

Maintenant, la requĂȘte lit seulement idx_address2 et n'affecte pas la table address.

Utilisation des index Ă  valeurs multiples (Multi-Value Indexes)

Certain columns that need to be indexed may not contain scalar data types. Column types like jsonb, arrays et tsvector can contain composite or multiple values. If you need to index such columns, you usually have to search through all individual values in these columns.

Let's try to find the titles of all films containing cuts from failed takes. In the table film there is a text column called special_features. If a film has this 'special feature', then the column contains an element in the form of a text array Behind The Scenes. To search for all such films, we need to select all rows with 'Behind The Scenes' for any array values special_features:

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

The containment operator @> checks whether the right side is a subset of the left side.

Query plan:

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)

Which asks for a full heap scan at a cost of 67.

Let's see if a regular B-tree index helps us:

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)

The index wasn't even considered. The B-tree index does not infer the existence of individual elements in indexed values.

We need a GIN index.

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)

The GIN index supports matching individual values to indexed composite values, resulting in a query plan cost reduction of more than half.

Let’s eliminate index duplication.

Les index s'accumulent avec le temps, et parfois un nouvel index peut contenir la mĂȘme dĂ©finition qu'un des prĂ©cĂ©dents. Pour obtenir des dĂ©finitions d'index lisibles par l'homme, vous pouvez utiliser la vue systĂšme. pg_indexes. Vous pourrez Ă©galement facilement trouver des dĂ©finitions identiques :

 SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
    FROM pg_indexes
GROUP BY defn
  HAVING count(*) > 1;
Et voici le résultat lorsqu'il est exécuté sur la base de données pagila par défaut :
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 lignes)

Index de sur-ensembles (Superset Indexes)

Il se peut que vous accumuliez de nombreux index, dont l'un indexe un sur-ensemble de colonnes qui sont dĂ©jĂ  indexĂ©es par d'autres index. Cela peut ĂȘtre souhaitable ou non — un sur-ensemble peut mener Ă  un scan uniquement par les index, ce qui est bien, mais il peut aussi prendre trop de place, ou bien la requĂȘte que cet ensemble devait optimiser n'est plus utilisĂ©e.

Si vous devez automatiser la détermination de tels index, vous pouvez commencer par pg_index de la table pg_catalog.

Index non utilisés

Avec l'Ă©volution des applications qui utilisent des bases de donnĂ©es, les requĂȘtes qu'elles utilisent Ă©voluent Ă©galement. Les index ajoutĂ©s prĂ©cĂ©demment peuvent ne plus ĂȘtre utilisĂ©s par aucune requĂȘte. À chaque scan d'index, il est marquĂ© par le gestionnaire de statistiques, et dans la vue du catalogue systĂšme pg_stat_user_indexes vous pouvez voir la valeur idx_scan, qui est un compteur cumulatif. Suivre cette valeur sur une pĂ©riode donnĂ©e (disons, un mois) donnera une bonne idĂ©e des index qui ne sont pas utilisĂ©s et qui peuvent ĂȘtre supprimĂ©s.

Voici la requĂȘte pour obtenir les compteurs de scan actuels de tous les index dans le schĂ©ma 'public':

SELECT relname, indexrelname, idx_scan
FROM   pg_catalog.pg_stat_user_indexes
WHERE  schemaname = 'public';
avec une sortie comme ceci :
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 lignes)

Recréer des index avec moins de blocages

Il arrive souvent qu'il faille recrĂ©er des index, par exemple lorsqu'ils gonflent en taille, et leur recrĂ©ation peut accĂ©lĂ©rer le scan. De plus, les index peuvent ĂȘtre corrompus. Modifier les paramĂštres d'un index peut Ă©galement nĂ©cessiter sa recrĂ©ation.

Activer la création d'index en parallÚle

Dans PostgreSQL 11, la crĂ©ation d'un index B-Tree est concurrente. Pour accĂ©lĂ©rer le processus de crĂ©ation, plusieurs travailleurs peuvent ĂȘtre utilisĂ©s en parallĂšle. Assurez-vous cependant que ces paramĂštres de configuration sont correctement dĂ©finis :

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

Les valeurs par dĂ©faut sont trop faibles. IdĂ©alement, ces chiffres devraient augmenter avec le nombre de cƓurs de processeur. Lisez-en plus dans documentation.

Création d'index en arriÚre-plan

Vous pouvez créer un index en arriÚre-plan en utilisant le paramÚtre CONCURRENTLY des commandes CREATE INDEX:

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

Cette procédure de création d'index se distingue de la normale en ce sens qu'elle ne nécessite pas de verrouillage de la table, ce qui signifie qu'elle ne bloque pas les opérations d'écriture. D'un autre cÎté, elle prend plus de temps et consomme plus de ressources.

Postgres offre de nombreuses options flexibles pour la crĂ©ation d'index et des solutions Ă  tous les cas particuliers, ainsi que des moyens de gĂ©rer la base de donnĂ©es en cas de croissance explosive de votre application. Nous espĂ©rons que ces conseils vous aideront Ă  rendre vos requĂȘtes rapides et votre base prĂȘte Ă  Ă©voluer.

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster