Choisir le bon index : un guide des types d'index PostgreSQL
Choisissez les types d'index PostgreSQL pour les requêtes d'égalité, de plage, JSONB, tableaux, texte intégral, spatiales et de séries temporelles volumineuses.
Choisir le bon index : un guide des types d'index PostgreSQL
Le mauvais index PostgreSQL peut gaspiller de l'espace disque, ralentir les écritures et laisser votre requête analyser des millions de lignes. Le bon index dépend de l'opérateur dans votre clause WHERE, du type de colonne et de la forme de vos données.
Commencez par B-tree pour les recherches normales d'égalité et de plage. Utilisez GIN, GiST, BRIN ou SP-GiST lorsque votre modèle de requête nécessite leur support d'opérateur spécifique.
L'importance de l'indexation dans PostgreSQL
Au cœur de PostgreSQL, l'indexation consiste à réduire la quantité de données à examiner pour satisfaire une requête. Sans index, PostgreSQL devrait effectuer un scan complet de table pour de nombreuses requêtes, ce qui peut être extrêmement lent, en particulier pour les grandes tables. Les index créent une structure de données qui permet à la base de données de localiser rapidement les lignes pertinentes. L'efficacité d'un index dépend fortement de :
- Le type d'index utilisé : Différents types d'index sont adaptés à différentes structures de données et opérations de requête.
- La distribution des données : Des données asymétriques peuvent impacter les performances de l'index.
- Les modèles de requête : La manière dont vous interrogez vos données est un facteur important.
Voici les types d'index parmi lesquels vous aurez le plus souvent à choisir.
Types d'index PostgreSQL expliqués
PostgreSQL propose plusieurs types d'index. Les plus utiles pour le travail de performance quotidien sont B-tree, GIN, GiST, BRIN et SP-GiST.
1. Index B-Tree
B-tree est le type d'index par défaut et le plus polyvalent de PostgreSQL. Il convient aux opérateurs de comparaison courants, y compris =, <, >, <= et >=. Les index B-tree sont excellents pour les vérifications d'égalité, les scans de plage, le tri, les contraintes uniques et les clés primaires.
Comment ça marche : Un index B-Tree stocke les données dans une structure arborescente triée. Chaque nœud de l'arbre contient des clés et des pointeurs vers les nœuds enfants. Cette structure garantit que la recherche, l'insertion et la suppression de données sont efficaces, généralement avec une complexité temporelle logarithmique.
Cas d'utilisation :
- Recherches d'égalité (
WHERE colonne = valeur) - Requêtes de plage (
WHERE colonne BETWEEN valeur1 AND valeur2ouWHERE colonne > valeur) - Tri (
ORDER BY colonne) - Recherche de la valeur minimale ou maximale (
ORDER BY colonne LIMIT 1) - Contraintes uniques et clés primaires (qui utilisent implicitement B-Tree)
Exemple :
Considérons une table utilisateurs avec des millions d'enregistrements. Indexer la colonne email à l'aide d'un B-Tree accélérera considérablement les recherches d'un utilisateur spécifique par son adresse e-mail.
CREATE INDEX idx_utilisateurs_email ON utilisateurs (email);
-- Maintenant, les requêtes comme celle-ci seront beaucoup plus rapides :
SELECT * FROM utilisateurs WHERE email = '[email protected]';
Astuce : Les index B-Tree sont généralement un bon point de départ et sont souvent suffisants pour de nombreuses opérations courantes de base de données. Cependant, pour des cas d'utilisation spécifiques comme la recherche en texte intégral ou les données géospatiales, d'autres types d'index peuvent être plus performants.
2. Index GIN (Generalized Inverted Index)
Les index GIN sont conçus pour indexer des valeurs composites ou des valeurs contenant plusieurs éléments, tels que des tableaux, des documents JSON ou des documents de recherche en texte intégral (tsvector). Ils sont particulièrement efficaces pour les requêtes qui recherchent la présence d'éléments spécifiques dans ces valeurs composites.
Comment ça marche : Un index GIN mappe chaque élément d'une valeur composite à une liste de lignes contenant cet élément. C'est un index inversé, ce qui signifie qu'il indexe les valeurs elles-mêmes plutôt que les lignes directement. Cela le rend efficace pour vérifier si un élément particulier existe dans une structure plus grande.
Cas d'utilisation :
- Recherche en texte intégral (
tsvectorvs.tsquery) - Indexation de tableaux (opérateurs
ANY,@>) - Indexation de données JSONB (opérateurs
?,?|,?&,@>,<@)
Exemple :
Supposons que vous ayez une table documents avec une colonne tags de type ARRAY de chaînes. Vous voulez trouver tous les documents étiquetés avec 'base de données'.
CREATE INDEX idx_documents_tags ON documents USING GIN (tags);
-- Requête pour trouver les documents avec l'étiquette 'base de données' :
SELECT * FROM documents WHERE tags @> ARRAY['base de données'];
-- Ou pour JSONB :
CREATE TABLE produits (id SERIAL PRIMARY KEY, details JSONB);
CREATE INDEX idx_produits_details ON produits USING GIN (details);
SELECT * FROM produits WHERE details ? 'fabricant';
Remarque : Les index GIN peuvent être plus lents à mettre à jour que les index B-Tree car ils doivent réindexer chaque élément. Cependant, ils offrent des performances de requête supérieures pour les recherches impliquant des éléments dans des types composites.
3. Index GiST (Generalized Search Tree)
Les index GiST sont un framework qui permet la création de types d'index personnalisés. Ils sont couramment utilisés pour indexer des types de données géométriques et pour la recherche en texte intégral. Les index GiST sont particulièrement utiles lorsque les données sont complexes et ne s'intègrent pas facilement dans une structure B-Tree.
Comment ça marche : GiST est une méthode d'indexation très flexible. Elle fonctionne en partitionnant récursivement l'espace de données. Bien que la structure interne puisse varier en fonction de la classe d'opérateur spécifique utilisée, elle organise généralement les données dans une structure arborescente.
Cas d'utilisation :
- Types de données géométriques (points, lignes, polygones) pour les requêtes spatiales (
&&,@>). - Indexation de plages.
- Recherche en texte intégral avec des classes d'opérateurs GiST.
Exemple :
Pour l'indexation spatiale, imaginez une table de points d'intérêt (POI) et vous voulez trouver tous les POI dans une certaine zone géographique.
CREATE TABLE pois (
id SERIAL PRIMARY KEY,
nom VARCHAR(100),
localisation GEOMETRY(Point, 4326) -- Utilisation de l'extension PostGIS
);
-- Créer un index GiST sur la colonne localisation
CREATE INDEX idx_pois_localisation ON pois USING GIST (localisation);
-- Trouver les POI dans une boîte englobante (exemple utilisant les fonctions PostGIS)
SELECT * FROM pois WHERE ST_Intersects(localisation, ST_MakeEnvelope(lon1, lat1, lon2, lat2, 4326));
Astuce : Les index GiST sont puissants pour les types de données complexes et les requêtes spatiales. Ils peuvent également être utilisés pour les index partiels, indexant uniquement un sous-ensemble de lignes basé sur une condition, ce qui peut optimiser davantage les performances.
4. Index BRIN (Block Range INdex)
Les index BRIN sont conçus pour les très grandes tables où les données ont une corrélation naturelle avec leur emplacement de stockage physique sur le disque. Ils fonctionnent en indexant des plages d'adresses de blocs physiques plutôt que des valeurs de lignes individuelles. Cela les rend très petits et rapides à créer, mais efficaces uniquement si les valeurs de la colonne indexée sont corrélées avec leur ordre physique.
Comment ça marche : Un index BRIN stocke les valeurs minimales et maximales pour une plage de blocs de table. Lors de l'interrogation, PostgreSQL vérifie les valeurs min/max pour une plage de blocs. Si la condition de la requête tombe en dehors de cette plage, toute la plage de blocs est ignorée, évitant un scan complet de table. C'est plus efficace pour les données naturellement ordonnées comme les horodatages ou les ID de séquence.
Cas d'utilisation :
- Très grandes tables.
- Colonnes avec une forte corrélation naturelle avec leur ordre de stockage physique (par exemple, horodatages
created_at, ID auto-incrémentés). - Lorsque la plage de valeurs dans un bloc est significativement plus petite que le nombre de lignes dans ce bloc.
Exemple :
Considérons une table de logs avec des milliards d'entrées, ordonnées par horodatage.
CREATE TABLE logs (
id BIGSERIAL PRIMARY KEY,
message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Créer un index BRIN sur created_at
CREATE INDEX idx_logs_created_at ON logs USING BRIN (created_at);
-- Requête pour les logs d'un jour spécifique :
SELECT * FROM logs WHERE created_at >= '2023-10-26 00:00:00' AND created_at < '2023-10-27 00:00:00';
Avertissement : Les index BRIN ne sont efficaces que si les données sont physiquement ordonnées. Si les données sont insérées dans un ordre aléatoire, ou si les valeurs des colonnes ne sont pas corrélées avec leur emplacement physique, les index BRIN n'apporteront pas d'avantages significatifs en termes de performances et peuvent même les dégrader. Le paramètre pages_per_range peut être ajusté pour optimiser l'efficacité de l'index BRIN.
5. Index SP-GiST (Space-Partitioned Generalized Search Tree)
SP-GiST est un autre type d'arbre de recherche généralisé, similaire à GiST, mais optimisé pour les algorithmes qui partitionnent l'espace de manière déséquilibrée. Il est particulièrement utile pour indexer des distributions de données non uniformes et des structures de données spatiales complexes comme les quadtrees ou les k-d trees.
Comment ça marche : SP-GiST utilise une variété de stratégies de partitionnement, ce qui le rend adaptable à différents types de données et modèles de requête. Il peut être plus efficace que GiST pour certains types de données, en particulier lorsqu'il s'agit d'ensembles de données ayant une distribution très regroupée ou clairsemée.
Cas d'utilisation :
- Données ponctuelles avec k-d trees ou quadtrees.
- Données réseau.
- Données géospatiales.
- Recherche textuelle.
Exemple :
Bien que souvent utilisé pour des structures géométriques complexes, un cas d'utilisation courant implique l'indexation d'un grand ensemble de points.
-- Supposons une table avec des coordonnées de points
CREATE TABLE points (id SERIAL PRIMARY KEY, coord POINT);
-- Créer un index SP-GiST
CREATE INDEX idx_points_coord ON points USING SPGIST (coord);
-- Requête pour les points dans une certaine région
SELECT * FROM points WHERE coord <@ box '((x1,y1),(x2,y2))';
Considération : Les index SP-GiST peuvent offrir des avantages de performance pour des structures de données et des modèles de requête spécifiques où les B-Tree traditionnels ou même GiST pourraient avoir du mal. Cependant, leur complexité signifie qu'ils ne sont pas toujours le premier choix à moins que des benchmarks spécifiques n'indiquent un avantage.
Autres types d'index (brièvement)
- Index Hash : Ne prennent en charge que les comparaisons d'égalité (
=). Ils sont journalisés dans le WAL dans les versions modernes de PostgreSQL, mais les index B-tree restent le premier choix habituel car ils prennent en charge plus d'opérateurs et le tri. - Index Partiels : Ces index n'indexent qu'un sous-ensemble des lignes de la table qui satisfont une clause
WHERE. Ils peuvent économiser de l'espace et améliorer les performances si les requêtes ciblent fréquemment un sous-ensemble spécifique de données. - Index d'Expression : Vous pouvez créer des index sur des expressions ou des fonctions d'une ou plusieurs colonnes. Ceci est utile pour les requêtes qui utilisent fréquemment ces expressions dans les clauses
WHERE, commelower(email).
Quand utiliser quel type d'index ?
Choisir le bon index est une partie cruciale de l'optimisation des performances de PostgreSQL. Voici un guide rapide pour vous aider à décider :
| Type d'index | Meilleur pour | Opérateurs pris en charge | Considérations |
|---|---|---|---|
| B-Tree | Usage général, égalité, plage, tri | =, <, >, <=, >= |
Par défaut, polyvalent, bon tout-terrain. |
| GIN | Recherche en texte intégral, tableaux, JSONB, types composites | @@, @>, <@, ?, `? |
, ?&` |
| GiST | Données spatiales, types géométriques, recherche en texte intégral | &&, @>, <@, @@ (et d'autres via les classes d'opérateurs) |
Flexible, bon pour les structures de données complexes, peut être plus lent que B-Tree. |
| BRIN | Très grandes tables avec données physiquement corrélées | <, >, <=, >=, = |
Taille réduite, création rapide, efficace uniquement avec une corrélation de données ordonnée. |
| SP-GiST | Données non uniformes, structures spatiales complexes | Varie selon la classe d'opérateur (par exemple, spatial, réseau) | Efficace pour certaines stratégies de partitionnement, peut être plus complexe à régler. |
Facteurs à considérer :
- Modèles de requête : Quels types de requêtes exécutez-vous le plus souvent ? S'agit-il de vérifications d'égalité, de scans de plage, de recherches en texte intégral ou de requêtes spatiales ?
- Type de données : Le type de données indexées (par exemple, chaînes, nombres, tableaux, JSON, points géométriques) influence fortement le meilleur choix d'index.
- Distribution des données : Vos données sont-elles naturellement ordonnées (comme les horodatages) ou distribuées aléatoirement ?
- Fréquence de mise à jour : À quelle fréquence les données dans les colonnes indexées sont-elles mises à jour ? Les index GIN et GiST peuvent être plus lents à mettre à jour que les B-Tree.
- Taille de la table : Pour les tables extrêmement grandes, les index BRIN peuvent être avantageux s'il existe une corrélation des données.
- Taille et maintenance de l'index : Considérez l'espace disque requis pour l'index et la surcharge de sa maintenance.
Création et gestion des index
PostgreSQL fournit des commandes SQL simples pour gérer les index :
Créer un index :
CREATE INDEX nom_index ON nom_table USING type_index (nom_colonne [ASC|DESC] [NULLS FIRST|LAST], ...);Supprimer un index :
DROP INDEX nom_index;Afficher les index existants :
\d+ nom_table;
Meilleure pratique : Testez toujours l'impact sur les performances de la création ou de la modification d'index dans un environnement de staging avant d'appliquer les modifications en production. Utilisez EXPLAIN ANALYZE pour comprendre comment vos requêtes utilisent les index.
À retenir
Choisissez l'index qui correspond à votre opérateur et à la forme de vos données, puis prouvez-le avec EXPLAIN ANALYZE. Les index font également partie du chemin d'écriture, alors gardez ceux qui servent de vraies requêtes et supprimez ceux qui n'ajoutent que des coûts de maintenance.