Utilisation de toutes les fonctionnalités des index dans PostgreSQL


Dans le monde Postgres, les index sont cruciaux pour naviguer efficacement dans un référentiel de base de données (appelé tas, tas). Postgres ne prend pas en charge le clustering pour cela, et l'architecture MVCC vous fait accumuler de nombreuses versions du même tuple. Par conséquent, il est très important de pouvoir créer et maintenir des index efficaces pour prendre en charge les applications.

Voici quelques conseils pour optimiser et améliorer l'utilisation des index.

Remarque: les requêtes présentées ci-dessous fonctionnent sur une base de données exemple pagila non modifiée.

Utilisation des index de couverture


Examinons la demande de récupération des adresses e-mail des utilisateurs inactifs. La table customer a une colonne active et la demande est simple:

 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) 

La requête appelle la séquence complète de l'analyse de la table customer . Cré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; QUERY PLAN ----------------------------------------------------------------------------- Index Scan using idx_cust1 on customer (cost=0.28..12.29 rows=15 width=32) Index Cond: (active = 0) (2 rows) 

Cela a aidé, l'analyse suivante s'est transformée en une " index scan ". Cela signifie que Postgres analysera l'index idx_cust1 , puis continuera à rechercher le tas de la table pour lire les valeurs des autres colonnes (dans ce cas, la colonne email - email ) dont la requête a besoin.

PostgreSQL 11 a introduit les index de couverture. Ils vous permettent d'inclure une ou plusieurs colonnes supplémentaires dans l'index lui-même - leurs valeurs sont stockées dans le magasin de données d'index.

Si nous avons utilisé cette fonctionnalité et ajouté une valeur de courrier électronique à l'intérieur de l'index, Postgres n'aurait pas besoin de rechercher la valeur de email dans le tas de table. Voyons voir si cela fonctionne:

 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 " nous indique que la requête n'a désormais besoin que d'un seul index, ce qui évite à toutes les E / S disque de lire le tas de table.

Aujourd'hui, les indices de couverture ne sont disponibles que pour les arbres B. Cependant, dans ce cas, les efforts d'escorte seront plus importants.

Utilisation d'index partiels


Les index partiels indexent uniquement un sous-ensemble des lignes d'une table. Cela permet d'économiser la taille des index et des analyses plus rapides.

Supposons que nous ayons besoin d'obtenir une liste d'adresses e-mail de nos clients californiens. La demande sera comme ceci:

 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) 

Quels indices ordinaires nous donneront:

 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) 

L'analyse d' address été remplacée par l' idx_address1 index idx_address1 , puis le idx_address1 address été analysé.

Comme il s'agit d'une requête fréquente et doit être optimisée, nous pouvons utiliser un index partiel qui indexe uniquement les lignes avec des adresses dans lesquelles la région '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) 

Désormais, la demande ne lit que idx_address2 et ne touche pas la table d' address .

Utilisation d'index multi-valeurs


Certaines colonnes qui doivent être indexées peuvent ne pas contenir de type de données scalaire. jsonb types de jsonb tels que jsonb , arrays et tsvector contiennent plusieurs ou plusieurs valeurs. Si vous devez indexer de telles colonnes, vous devez généralement rechercher toutes les valeurs individuelles dans ces colonnes.

Essayons de trouver les noms de tous les films contenant des coupures de prises infructueuses. La table de film a une colonne de texte appelée special_features . Si le film possède cette «propriété spéciale», la colonne contient un élément sous la forme d'un tableau de texte Behind The Scenes . Pour rechercher tous ces films, nous devons sélectionner toutes les lignes avec «Dans les coulisses» pour toutes les valeurs du tableau special_features :

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

L'opérateur de confinement @> vérifie si le côté droit est un sous-ensemble du côté gauche.

Plan de demande:

 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) 

Qui demande une analyse complète du tas avec un coût de 67.

Voyons si l'index B-tree régulier nous aide:

 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) 

L'indice n'a même pas été pris en compte. L'index de l'arbre B n'est pas conscient de l'existence d'éléments individuels dans les valeurs indexées.

Nous avons besoin d'un 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) 

GIN-index prend en charge la comparaison des valeurs individuelles avec les valeurs composites indexées, par conséquent, le coût du plan de requête est réduit de plus de moitié.

Supprimer les index en double


Les index s'accumulent avec le temps, et parfois un nouvel index peut contenir la même définition que l'un des précédents. Pour obtenir des définitions SQL lisibles par l'homme des index, vous pouvez utiliser la vue catalogue pg_indexes . Vous pouvez également retrouver facilement les mêmes définitions:

  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) 

Index de surensemble


Il peut arriver que vous accumuliez de nombreux index, dont l'un indexe un sous-ensemble des colonnes qui indexent d'autres index. Cela peut être souhaitable ou non - un sur-ensemble ne peut analyser que par index, ce qui est bien, mais il peut prendre trop de place, ou la requête pour laquelle ce sur-ensemble était destiné à être optimisé n'est plus utilisée.

Si vous avez besoin d'automatiser la définition de ces index, vous pouvez commencer avec pg_index à partir de la table pg_catalog .

Index inutilisés


À mesure que les applications qui utilisent des bases de données se développent, les requêtes qu'elles utilisent se développent également. Les index ajoutés précédemment ne peuvent plus être utilisés par aucune requête. Chaque fois que l'index est analysé, il est marqué par le gestionnaire de statistiques et dans la pg_stat_user_indexes catalogue système pg_stat_user_indexes vous pouvez voir la valeur idx_scan , qui est un compteur cumulatif. Le suivi de cette valeur sur une période de temps (disons un mois) donnera une bonne idée des index qui ne sont pas utilisés et qui peuvent être supprimés.

Voici une demande pour obtenir le nombre de scan actuel de tous les index dans le schéma '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) 

Recréer des index avec moins de verrous


Souvent, les index doivent être recréés, par exemple, lorsqu'ils sont gonflés en taille, et la recréation peut accélérer l'analyse. De plus, les indices peuvent être corrompus. La modification des paramètres d'index peut également nécessiter sa recréation.

Activer la création d'index parallèle


Dans PostgreSQL 11, la création d'un index B-Tree est compétitive. Pour accélérer le processus de création, plusieurs travailleurs parallèles peuvent être utilisés. Cependant, assurez-vous que ces paramètres de configuration sont définis correctement:

 SET max_parallel_workers = 32; SET max_parallel_maintenance_workers = 16; 

Les valeurs par défaut sont trop petites. Idéalement, ces nombres devraient être augmentés avec le nombre de cœurs de processeur. Lisez la documentation pour plus de détails.

Création d'un index en arrière-plan


Vous pouvez créer un index en arrière-plan à l'aide du paramètre CONCURRENTLY de la commande CREATE INDEX :

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


Cette procédure de création d'index diffère de la procédure habituelle en ce qu'elle ne nécessite pas de verrouillage de table et ne bloque donc pas les opérations d'écriture. En revanche, cela prend plus de temps et consomme plus de ressources.

Postgres fournit de nombreuses options flexibles pour créer des index et des moyens de résoudre des 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 de données prête à évoluer.

Source: https://habr.com/ru/post/fr453046/


All Articles