PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Remarques sur les index PostgreSQL B-Tree

PostgreSQL est livré avec pas moins de 6 types d'index différents, le B-Treeindex étant le plus couramment utilisé. Lisez la suite pour en savoir plus sur les index B-Tree dans PostgreSQL.

Types d'index

Un index dans PostgreSQL, comme ceux créés pour les PRIMARY KEYs et les UNIQUES dans une instruction CREATE TABLE ou créés explicitement avec une instruction CREATE INDEX, sont d'un "type" particulier (bien que techniquement nous devrions les appeler "méthodes d'accès à l'index").

PostgreSQL est livré avec ces types d'index intégrés :

  • B-Tree
  • Hachage
  • GIN – Indice inversé généralisé
  • BRIN – Indice de plage de blocs (uniquement dans la version 9.5 et supérieure)
  • GiST – Arbre de recherche inversé généralisé
  • SP-GiST – Espace partitionné GiST

B-Tree est le type d'index par défaut et le plus couramment utilisé. La spécification d'une clé primaire ou d'un unique dans une instruction CREATE TABLE force PostgreSQL à créer des index B-Tree. Les instructions CREATE INDEX sans la clause USING créeront également des index B-Tree :

-- the default index type is btree
CREATE INDEX ix_year ON movies (year);

-- equivalent, explicitly lists the index type
CREATE INDEX ix_year ON movies USING btree (year);

Commander

Les index B-Tree sont intrinsèquement ordonnés. PostgreSQL peut utiliser cet ordre plutôt que de trier sur l'expression indexée. Par exemple, obtenir les titres de tous les films des années 80 triés par titre nécessiterait un tri :

idxdemo=# explain select title from movies where year between 1980 and 1989 order by title asc;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Sort  (cost=240.79..245.93 rows=2056 width=17)
   Sort Key: title
   ->  Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=17)
         Index Cond: ((year >= 1980) AND (year <= 1989))
(4 rows)

Mais si vous les triez par colonne indexée (année), un tri supplémentaire n'est pas nécessaire.

idxdemo=# explain select title from movies where year between 1980 and 1989 order by year asc;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=21)
   Index Cond: ((year >= 1980) AND (year <= 1989))
(2 rows)

Facteur de remplissage

Pour les tables qui ne seront pas mises à jour, vous pouvez augmenter le "facteur de remplissage" à partir de la valeur par défaut de 90, ce qui devrait vous donner des index légèrement plus petits et plus rapides. Inversement, s'il y a des mises à jour fréquentes de la table impliquant le paramètre indexé, vous pouvez réduire le facteur de remplissage à un nombre plus petit - cela permettra des insertions et des mises à jour plus rapides, au prix d'index légèrement plus grands.

CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);

Indexation sur texte

Les index B-Tree peuvent aider à faire correspondre les préfixes du texte. Prenons une requête pour lister tous les films commençant par la lettre "T" :

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Ce plan nécessite une analyse séquentielle complète de la table. Que se passe-t-il si nous ajoutons un index B-Tree sur movies.title ?

idxdemo=> create index ix_title on movies (title);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Eh bien, cela n'a pas aidé du tout. Cependant, il existe une forme de poussière de lutin magique que nous pouvons saupoudrer pour que Postgres fasse ce que nous voulons :

idxdemo=> create index ix_title2 on movies (title text_pattern_ops);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Bitmap Heap Scan on movies  (cost=236.08..1085.19 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
   ->  Bitmap Index Scan on ix_title2  (cost=0.00..233.98 rows=8169 width=0)
         Index Cond: ((title ~>=~ 'T'::text) AND (title ~<~ 'U'::text))
(4 rows)

Le plan utilise maintenant un indice, et le coût a diminué. La magie ici est "text_pattern_ops" qui permet à l'index B-Tree sur une expression "texte" d'être utilisé pour les opérateurs de modèle (LIKE et expressions régulières). Le "text_pattern_ops" est appelé une OperatorClass.

Notez que cela ne fonctionnera que pour les modèles avec un préfixe de texte fixe, donc "% Angry%" ou "% Men" ne fonctionnera pas. Utilisez la recherche de texte intégral de PostgreSQL pour les requêtes de texte avancées.

Index de couverture

Les index de couverture ont été ajoutés à PostgreSQL dans la v11. Les index de couverture vous permettent d'inclure la valeur d'une ou plusieurs expressions avec l'expression indexée à l'intérieur de l'index.

Essayons de rechercher tous les titres de films, classés par année de sortie :

idxdemo=# explain select title from movies order by year asc;
                             QUERY PLAN
--------------------------------------------------------------------
 Sort  (cost=3167.73..3239.72 rows=28795 width=21)
   Sort Key: year
   ->  Seq Scan on movies  (cost=0.00..1034.95 rows=28795 width=21)
(3 rows)

Cela implique un balayage séquentiel complet de la table, suivi d'un tri des colonnes projetées. Commençons par ajouter un index régulier sur movies.year :

idxdemo=# create index ix_year on movies (year);
CREATE INDEX

idxdemo=# explain select title from movies order by year asc;
                                  QUERY PLAN
------------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..1510.22 rows=28795 width=21)
(1 row)

Maintenant, Postgres décide d'utiliser l'index pour extraire les entrées de la table directement dans l'ordre souhaité. La table doit être consultée car l'index ne contient que la valeur de "année" et la référence au tuple dans la table.

Si nous incluons également la valeur de 'title' dans l'index, la recherche de table peut être totalement évitée. Utilisons la nouvelle syntaxe pour créer un tel index :

idxdemo=# create index ix_year_cov on movies (year) include (title);
CREATE INDEX
Time: 92.618 ms

idxdemo=# drop index ix_year;
DROP INDEX

idxdemo=# explain select title from movies order by year asc;
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Index Only Scan using ix_year_cov on movies  (cost=0.29..2751.59 rows=28795 width=21)
(1 row)

Postgres utilise maintenant un Index OnlyScan, ce qui signifie que la recherche de table est totalement évitée. Notez que nous avons dû supprimer l'ancien index, car Postgres n'a pas choisi ix_year_cov plutôt que ix_year pour cette requête.

Clustering

PostgreSQL ne prend malheureusement pas en charge l'ordre physique automatique des lignes dans une table, contrairement aux «index clusterisés» dans d'autres SGBDR. Si la plupart de vos requêtes vont extraire la plupart des lignes d'une table principalement statique dans un ordre fixe, ce serait une bonne idée de disposer le stockage physique de la table dans cet ordre et d'utiliser des analyses séquentielles. Pour réorganiser physiquement une table dans l'ordre dicté par un index, utilisez :

CLUSTER VERBOSE movies USING ix_year;

Vous utiliserez généralement un index B-Tree pour regrouper une table, car il fournit un ordre complet pour toutes les lignes de la table.

Statistiques d'index

Combien d'espace disque votre index occupe-t-il ? La fonction pg_relation_size peut répondre à cela :

idxdemo=# select * from pg_relation_size('ix_year');
 pg_relation_size
------------------
           663552
(1 row)

Cela renvoie l'espace disque utilisé par l'index, en octets.

Plus d'informations sur l'index peuvent être recueillies à l'aide de l'extension standard pgstattuple. Avant d'utiliser les fonctions ci-dessous, vous devez faire un CREATE EXTENSION pgstattuple; dans la base de données concernée en tant que superutilisateur. L'utilisation de ces fonctions nécessite également des privilèges de superutilisateur.

Le pgstattuple renvoie, entre autres, l'espace inutilisé (free_space )et réutilisable (dead_tuple_len ) espace disque dans l'index. Cela peut être très utile pour décider d'exécuter un REINDEX pour réduire le gonflement de l'index.

idxdemo=# select * from pgstattuple('ix_year'::regclass);
-[ RECORD 1 ]------+-------
table_len          | 663552
tuple_count        | 28795
tuple_len          | 460720
tuple_percent      | 69.43
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 66232
free_percent       | 9.98

Le pgstattuple renvoie des informations spécifiques au B-Tree, y compris le niveau de l'arbre :

idxdemo=# select * from pgstatindex('ix_year'::regclass);
-[ RECORD 1 ]------+-------
version            | 2
tree_level         | 1
index_size         | 663552
root_block_no      | 3
internal_pages     | 1
leaf_pages         | 79
empty_pages        | 0
deleted_pages      | 0
avg_leaf_density   | 89.72
leaf_fragmentation | 0

Cela peut être utilisé pour décider d'ajuster ou non le facteur de remplissage de l'index.

Examiner le contenu de l'index B-Tree

Même le contenu du B-Tree peut être examiné directement, en utilisant l'extension pageinspect. L'utilisation de cette extension nécessite des privilèges de superutilisateur.

Voici les propriétés d'une seule page (ici, la 13e page) de l'index :

idxdemo=# select * from bt_page_stats('ix_year', 13);
-[ RECORD 1 ]-+-----
blkno         | 13
type          | l
live_items    | 367
dead_items    | 0
avg_item_size | 16
page_size     | 8192
free_size     | 808
btpo_prev     | 12
btpo_next     | 14
btpo          | 0
btpo_flags    | 1

Et voici le contenu réel de chaque élément (limité à 5 ici) dans la page :

idxdemo=# select * from bt_page_items('ix_year', 13) limit 5;
 itemoffset |   ctid   | itemlen | nulls | vars |          data
------------+----------+---------+-------+------+-------------------------
          1 | (104,40) |      16 | f     | f    | 86 07 00 00 00 00 00 00
          2 | (95,38)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          3 | (95,39)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          4 | (95,40)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          5 | (96,1)   |      16 | f     | f    | 86 07 00 00 00 00 00 00
(5 rows)

Et si vous envisagez d'écrire une requête pour agréger quelque chose sur chaque page, vous aurez également besoin du nombre total de pages dans la relation, qui peut être générée via pg_relpages depuis le pgstattuple extension :

idxdemo=# select pg_relpages('ix_year');
 pg_relpages
-------------
          81
(1 row)

Autres types d'index

Les index B-Tree sont des outils polyvalents pour optimiser les requêtes. Avec un peu d'expérimentation et de planification, il peut être utilisé pour améliorer considérablement les temps de réponse des applications et des tâches de rapport.

Les autres types d'index de PostgreSQL sont également utiles et peuvent être plus efficaces et performants que B-Tree dans des cas spécifiques. Cet article donne un aperçu rapide de tous les types.

Vous avez une astuce sur les index que vous aimeriez partager ? Laissez-les en commentaire ci-dessous !