Techniques d’indexation

Module J4

Dalibo SCOP

24.09

29 août 2024

Sur ce document

Formation Module J4
Titre Techniques d’indexation
Révision 24.09
PDF https://dali.bo/j4_pdf
EPUB https://dali.bo/j4_epub
HTML https://dali.bo/j4_html
Slides https://dali.bo/j4_slides
TP https://dali.bo/j4_tp
TP (solutions) https://dali.bo/j4_solutions

Licence Creative Commons CC-BY-NC-SA

Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :

  • Paternité
  • Pas d’utilisation commerciale
  • Partage des conditions initiales à l’identique

Marques déposées

PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.

Versions de PostgreSQL couvertes

Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 12 à 16.

Techniques d’indexation

Un index à l’ancienne

Introduction

  • Qu’est-ce qu’un index ?
  • Comment indexer une base ?
  • Les index B-tree dans PostgreSQL

Objectifs

  • Comprendre ce qu’est un index
  • Maîtriser le processus de création d’index
  • Connaître les différents types d’index B-tree et leurs cas d’usages

Introduction aux index

  • Uniquement destinés à l’optimisation
  • À gérer d’abord par le développeur
    • Markus Winand : SQL Performance Explained

Utilité d’un index

  • Un index permet de :
    • trouver un enregistrement dans une table directement
    • récupérer une série d’enregistrements dans une table
    • voire tout récupérer dans l’index (Index Only Scan)
  • Un index facilite :
    • certains tris
    • certains agrégats
  • Obligatoires et automatique pour clés primaires & unicité
    • conseillé pour clés étrangères (FK)

Index et lectures

Un index améliore les SELECT

  • Sans index :
=# SELECT * FROM test WHERE id = 10000;
Temps : 1760,017 ms
  • Avec index :
=# CREATE INDEX idx_test_id ON test (id);

=# SELECT * FROM test WHERE id = 10000;
Temps : 27,711 ms

Index : inconvénients

  • L’index n’est pas gratuit !
  • Ralentit les écritures
    • maintenance
  • Place disque
  • Compromis à trouver

Index : contraintes pratiques à la création

  • Lourd…
-- bloque les écritures !
CREATE INDEX ON matable ( macolonne ) ;
-- ne bloque pas, peut échouer
CREATE INDEX CONCURRENTLY ON matable ( macolonne ) ;
  • Si fragmentation :
REINDEX INDEX nomindex ;
REINDEX TABLE CONCURRENTLY nomtable ;
  • Paramètres :
    • maintenance_work_mem (sinon : fichier temporaire !)
    • max_parallel_maintenance_workers

Types d’index dans PostgreSQL

  • Défaut : B-tree classique (équilibré)
  • UNIQUE (préférer la contrainte)
  • Mais aussi multicolonne, fonctionnel, partiel, couvrant
  • Index spécialisés : hash, GiST, GIN, BRIN, HNSW….

Fonctionnement d’un index

Un index à l’ancienne

Structure d’un index

  • Structure associant des clés (termes) à des localisations (pages)
  • Structure de données spécialisée, plusieurs types
  • Séparée de la table
  • Analogies :
    • fiches en carton des bibliothèques avant l’informatique (B-tree)
    • index d’un livre technique (GIN)

Un index n’est pas magique

  • Un index ne résout pas tout
  • Importance de la conception du schéma de données
  • Importance de l’écriture de requêtes SQL correctes

Index B-tree

  • Type d’index le plus courant
    • et le plus simple
  • Utilisable pour les contraintes d’unicité
  • Supporte les opérateurs : <, <=, =, >=, >
  • Supporte le tri
  • Ne peut pas indexer des colonnes de plus de 2,6 ko

Exemple de structure d’index

SELECT name FROM ma_table WHERE id = 22

Organisation d’un index B-tree

Index multicolonnes

  • Possibilité d’indexer plusieurs colonnes :
    CREATE INDEX ON ma_table (id, name) ;
  • Ordre des colonnes primordial
    • accès direct aux premières colonnes de l’index
    • pour les autres, PostgreSQL lira tout l’index ou ignorera l’index

Nœuds des index

  • Index Scan
  • Bitmap Scan
  • Index Only Scan
    • idéal pour les performances
  • et les variantes parallélisées

Méthodologie de création d’index

  • On indexe pour une requête
    • ou idéalement une collection de requêtes
  • Et pas « une table »

L’index ? Quel index ?

  • Identifier les requêtes nécessitant un index
  • Créer les index permettant de répondre à ces requêtes
  • Valider le fonctionnement, en rejouant la requête avec :
     EXPLAIN (ANALYZE, BUFFERS)

Index et clés étrangères

  • Indexation des colonnes faisant référence à une autre
  • Performances des DML
  • Performances des jointures

Index inutilisé

C’est souvent tout à fait normal

  • Utiliser l’index est-il rentable ?
  • La requête est-elle compatible ?
  • Bug de l’optimiseur : rare

Index utilisable mais non utilisé

  • L’optimiseur pense qu’il n’est pas rentable
    • sélectivité trop faible
    • meilleur chemin pour remplir d’autres critères
    • index redondant
    • Index Only Scan nécessite un VACUUM fréquent
  • Les estimations de volumétries doivent être assez bonnes !
    • statistiques récentes, précises

Index inutilisable à cause d’une fonction

  • Pas le bon type (CAST plus ou moins explicite)
EXPLAIN SELECT * FROM clients WHERE client_id = 3::numeric;
  • Utilisation de fonctions, comme :
SELECT * FROM ma_table WHERE to_char(ma_date, 'YYYY')='2014' ;

Index inutilisable à cause d’un LIKE ‘…%’

SELECT * FROM fournisseurs WHERE commentaire LIKE 'ipsum%';
  • Solution :
CREATE INDEX idx1 ON ma_table (col_varchar varchar_pattern_ops) ;

Index inutilisable car invalide

  • CREATE INDEX … CONCURRENTLY peut échouer

Indexation B-tree avancée

De nombreuses possibilités d’indexation avancée :

  • Index partiels
  • Index fonctionnels
  • Index couvrants
  • Classes d’opérateur

Index partiels

  • N’indexe qu’une partie des données :
CREATE INDEX on evenements (type) WHERE  traite IS FALSE ;
  • Ne sert que si la clause est logiquement équivalente !
    • ou partie de la clause (inégalités, IN)
  • Intérêt : index beaucoup plus petit

Index partiels : cas d’usage

  • Données chaudes et froides
  • Index dédié à une requête avec une condition fixe

Index partiels : utilisation

  • Éviter les index de type :
CREATE INDEX ON matable ( champ_filtre ) WHERE champ_filtre =
  • Préférer :
CREATE INDEX ON matable ( champ_resultat ) WHERE champ_filtre =

Index fonctionnels : principe

  • Un index sur a est inutilisable pour :
  SELECTWHERE upper(a)='DUPOND'
  • Indexer le résultat de la fonction :
   CREATE INDEX mon_idx ON ma_table (upper(a)) ;

Index fonctionnels : conditions

  • Critère identique à la fonction dans l’index
  • Fonction impérativement IMMUTABLE !
    • délicat avec les conversions de dates/heures
  • Ne pas espérer d’Index Only Scan

Index fonctionnels : maintenance

  • Ne pas oublier ANALYZE après création d’un index fonctionnel
    • les statistiques peuvent même être l’intérêt majeur (<v14)
  • La fonction ne doit jamais tomber en erreur
  • Si modification de la fonction
    • réindexation

Index couvrants : principe

  • But : obtenir un Index Only Scan
CREATE UNIQUE INDEX clients_idx1
ON clients (id_client) INCLUDE (nom_client) ;
  • Répondent à la clause WHERE
  • ET contiennent toutes les colonnes demandées par la requête :
SELECT id_client,nom_client FROM clients WHERE id_client > 100 ;
  • …si l’index n’est pas trop gros
    • à comparer à un index multicolonne

Classes d’opérateurs

  • Un index utilise des opérateurs de comparaison
  • Texte : différentes collations = différents tris… complexes
    • Index inutilisable sur :
    WHERE col_varchar LIKE 'chaine%'
  • Solution : opérateur varchar_pattern_ops :
    • force le tri caractère par caractère, sans la collation
    CREATE INDEX idx1
    ON ma_table (col_varchar varchar_pattern_ops)
  • Plus généralement :
    • nombreux autres opérateurs pour d’autres types d’index

Conclusion

  • Responsabilité de l’indexation
  • Compréhension des mécanismes
  • Différents types d’index, différentes stratégies

Quiz

Travaux pratiques

Index « simples »

Sélectivité

Index partiels

Index fonctionnels

Cas d’index non utilisés

Travaux pratiques (solutions)