Présentation
Cette série d'articles concerne en grande partie les index de PostgreSQL.
Tout sujet peut être considéré sous différents angles. Nous discuterons des sujets qui devraient intéresser un développeur d'applications qui utilise un SGBD: quels index sont disponibles, pourquoi il y en a tellement différents et comment les utiliser pour accélérer les requêtes. Le sujet peut probablement être couvert en moins de mots, mais en secret, nous espérons un développeur curieux, qui s'intéresse également aux détails des éléments internes, d'autant plus que la compréhension de ces détails vous permet non seulement de vous en remettre au jugement des autres, mais aussi de tirer des conclusions. de votre choix.
Le développement de nouveaux types d'index n'entre pas dans le champ d'application. Cela nécessite une connaissance du langage de programmation C et relève de l'expertise d'un programmeur système plutôt que d'un développeur d'applications. Pour la même raison, nous ne discuterons presque pas des interfaces de programmation, mais nous nous concentrerons uniquement sur ce qui compte pour travailler avec des index prêts à l'emploi.
Dans cet article, nous discuterons de la répartition des responsabilités entre le
moteur d'indexation général lié au cœur du SGBD et les méthodes d'accès aux index individuels, que PostgreSQL nous permet d'ajouter en tant qu'extensions. Dans le prochain article, nous discuterons de l'
interface de la méthode d'accès et des concepts critiques tels que les classes et les familles d'opérateurs. Après cette introduction longue mais nécessaire, nous examinerons les détails de la structure et de l'application des différents types d'index:
Hash ,
B-tree ,
GiST ,
SP-GiST ,
GIN and
RUM ,
BRIN et
Bloom .
Avant de commencer, je voudrais remercier Elena Indrupskaya d'avoir traduit les articles en anglais.
Les choses ont un peu changé depuis la publication originale. Mes commentaires sur l'état actuel des choses sont indiqués comme ceci.
Index
Dans PostgreSQL, les index sont des objets de base de données spéciaux principalement conçus pour accélérer l'accès aux données. Ce sont des structures auxiliaires: chaque index peut être supprimé et recréé à partir des informations du tableau. Vous pouvez parfois entendre qu'un SGBD peut fonctionner sans index bien que lentement. Cependant, ce n'est pas le cas, car les index servent également à appliquer certaines contraintes d'intégrité.
À l'heure actuelle, six types d'index différents sont intégrés à PostgreSQL 9.6, et un index supplémentaire est disponible en tant qu'extension - grâce à d'importants changements dans la version 9.6. Attendez-vous donc à de nouveaux types d'index dans un avenir proche.
Malgré toutes les différences entre les types d'index (également appelés méthodes d'accès), chacun d'eux finit par associer une clé (par exemple, la valeur de la colonne indexée) aux lignes de table qui contiennent cette clé. Chaque ligne est identifiée par TID (tuple id), qui se compose du nombre de blocs dans le fichier et de la position de la ligne à l'intérieur du bloc. Cela dit, avec la clé connue ou certaines informations à ce sujet, nous pouvons lire rapidement les lignes qui peuvent contenir les informations qui nous intéressent sans parcourir la table entière.
Il est important de comprendre qu'un index accélère l'accès aux données à un certain coût de maintenance. Pour chaque opération sur les données indexées, que ce soit l'insertion, la suppression ou la mise à jour des lignes de table, les index de cette table doivent également être mis à jour et dans la même transaction. Notez que la mise à jour des champs de table pour lesquels les index n'ont pas été créés n'entraîne pas de mise à jour d'index; cette technique est appelée HOT (Heap-Only Tuples).
L'extensibilité comporte certaines implications. Pour permettre l'ajout facile d'une nouvelle méthode d'accès au système, une interface du moteur d'indexation général a été implémentée. Sa tâche principale est d'obtenir les TID de la méthode d'accès et de travailler avec eux:
- Lisez les données des versions correspondantes des lignes du tableau.
- Récupérez les versions de ligne TID par TID ou dans un lot à l'aide d'un bitmap prédéfini.
- Vérifiez la visibilité des versions de ligne pour la transaction en cours en tenant compte de son niveau d'isolement.
Le moteur d'indexation est impliqué dans l'exécution des requêtes. Il est appelé selon un plan créé au stade de l'optimisation. L'optimiseur, triant et évaluant les différentes manières d'exécuter la requête, doit comprendre les capacités de toutes les méthodes d'accès potentiellement applicables. La méthode pourra-t-elle renvoyer les données dans l'ordre requis ou devrions-nous anticiper le tri? Pouvons-nous utiliser cette méthode pour rechercher NULL? Ce sont des problèmes que l'optimiseur résout régulièrement.
Non seulement l'optimiseur a besoin d'informations sur la méthode d'accès. Lors de la construction d'un index, le système doit décider si l'index peut être construit sur plusieurs colonnes et si cet index garantit l'unicité.
Ainsi, chaque méthode d'accès doit fournir toutes les informations nécessaires sur elle-même. Les versions inférieures à 9.6 utilisaient pour cela la table "pg_am", tandis qu'à partir de la version 9.6 les données étaient déplacées à des niveaux plus profonds, à l'intérieur de fonctions spéciales. Nous nous familiariserons un peu plus avec cette interface.
Tout le reste est la tâche de la méthode d'accès:
- Implémentez un algorithme pour construire l'index et mapper les données en pages (pour que le gestionnaire de cache de tampon traite uniformément chaque index).
- Rechercher des informations dans l'index par un prédicat sous la forme " expression d'opérateur de champ indexé ".
- Évaluez le coût d'utilisation de l'index.
- Manipulez les verrous requis pour un traitement parallèle correct.
- Générez des enregistrements de journal d'écriture anticipée (WAL).
Nous examinerons d'abord les capacités du moteur d'indexation général, puis nous examinerons différentes méthodes d'accès.
Moteur d'indexation
Le moteur d'indexation permet à PostgreSQL de travailler de manière uniforme avec diverses méthodes d'accès, mais en tenant compte de leurs fonctionnalités.
Principales techniques de numérisation
Balayage d'index
Nous pouvons travailler différemment avec les TID fournis par un index. Prenons un exemple:
postgres=# create table t(a integer, b text, c boolean); postgres=# insert into t(a,b,c) select s.id, chr((32+random()*94)::integer), random() < 0.01 from generate_series(1,100000) as s(id) order by random(); postgres=# create index on t(a); postgres=# analyze t;
Nous avons créé une table à trois champs. Le premier champ contient des nombres de 1 à 100 000 et un index (quel que soit le type) est créé sur ce champ. Le deuxième champ contient divers caractères ASCII à l'exception de ceux non imprimables. Enfin, le troisième champ contient une valeur logique qui est vraie pour environ 1% des lignes et fausse pour le reste. Les lignes sont insérées dans le tableau dans un ordre aléatoire.
Essayons de sélectionner une valeur par la condition "a = 1". Notez que la condition ressemble à "
expression d'opérateur de champ indexé ", où l'
opérateur est "égal" et l'
expression (clé de recherche) est "1". Dans la plupart des cas, la condition doit ressembler à ceci pour que l'index soit utilisé.
postgres=# explain (costs off) select * from t where a = 1;
QUERY PLAN ------------------------------- Index Scan using t_a_idx on t Index Cond: (a = 1) (2 rows)
Dans ce cas, l'optimiseur a décidé d'utiliser le
scan d'index . Avec l'analyse d'index, la méthode d'accès renvoie les valeurs TID une par une jusqu'à ce que la dernière ligne correspondante soit atteinte. Le moteur d'indexation accède à tour de rôle aux lignes de table indiquées par les TID, obtient la version de ligne, vérifie sa visibilité par rapport aux règles de concurrence multiversionnelles et renvoie les données obtenues.
Numérisation bitmap
L'analyse d'index fonctionne correctement lorsque nous ne traitons que de quelques valeurs. Cependant, à mesure que le nombre de lignes récupérées augmente, il est plus probable qu'il revienne plusieurs fois sur la même page de table. Par conséquent, l'optimiseur passe à l'
analyse bitmap .
postgres=# explain (costs off) select * from t where a <= 100;
QUERY PLAN ------------------------------------ Bitmap Heap Scan on t Recheck Cond: (a <= 100) -> Bitmap Index Scan on t_a_idx Index Cond: (a <= 100) (4 rows)
La méthode d'accès renvoie d'abord tous les TID qui correspondent à la condition (nœud Bitmap Index Scan), et le bitmap des versions de ligne est construit à partir de ces TID. Les versions des lignes sont ensuite lues à partir du tableau (Bitmap Heap Scan), chaque page étant lue une seule fois.
Notez que dans la deuxième étape, la condition peut être revérifiée (Recheck Cond). Le nombre de lignes récupérées peut être trop grand pour que le bitmap des versions de lignes s'adapte parfaitement à la RAM (limité par le paramètre "work_mem"). Dans ce cas, le bitmap est uniquement créé pour les pages qui contiennent au moins une version de ligne correspondante. Ce bitmap "avec perte" nécessite moins d'espace, mais lors de la lecture d'une page, nous devons revérifier les conditions pour chaque ligne qu'il contient. Notez que même pour un petit nombre de lignes récupérées et donc le bitmap "exact" (comme dans notre exemple), l'étape "Recheck Cond" est de toute façon représentée dans le plan, bien qu'elle ne soit pas réellement effectuée.
Si des conditions sont imposées sur plusieurs champs de table et que ces champs sont indexés, le scan bitmap permet d'utiliser plusieurs index simultanément (si l'optimiseur le juge efficace). Pour chaque index, des bitmaps de versions de lignes sont créés, pour lesquels une multiplication booléenne au niveau du bit (si les expressions sont jointes par AND) ou une addition booléenne (si les expressions sont jointes par OR) est ensuite effectuée. Par exemple:
postgres=# create index on t(b); postgres=# analyze t; postgres=# explain (costs off) select * from t where a <= 100 and b = 'a';
QUERY PLAN -------------------------------------------------- Bitmap Heap Scan on t Recheck Cond: ((a <= 100) AND (b = 'a'::text)) -> BitmapAnd -> Bitmap Index Scan on t_a_idx Index Cond: (a <= 100) -> Bitmap Index Scan on t_b_idx Index Cond: (b = 'a'::text) (7 rows)
Ici, le nœud BitmapAnd joint deux bitmaps par l'opération "et" au niveau du bit.
Le scan bitmap nous permet d'éviter les accès répétés à la même page de données. Mais que se passe-t-il si les données des pages de table sont ordonnées physiquement exactement de la même manière que les enregistrements d'index? Il ne fait aucun doute que nous ne pouvons pas entièrement nous fier à l'ordre physique des données dans les pages. Si des données triées sont nécessaires, nous devons explicitement spécifier la clause ORDER BY dans la requête. Mais des situations sont susceptibles où en fait "presque toutes" les données sont ordonnées: par exemple, si des lignes sont ajoutées dans l'ordre nécessaire et ne changent pas après cela ou après avoir exécuté la commande CLUSTER. Dans des cas comme celui-ci, la construction d'un bitmap est une étape excessive, et un scan d'index régulier sera tout aussi bon (sauf si nous prenons en compte la possibilité de joindre plusieurs index). Par conséquent, lors du choix d'une méthode d'accès, le planificateur examine une statistique spéciale qui montre la corrélation entre l'ordre des lignes physiques et l'ordre logique des valeurs des colonnes:
postgres=# select attname, correlation from pg_stats where tablename = 't';
attname | correlation ---------+------------- b | 0.533512 c | 0.942365 a | -0.00768816 (3 rows)
Des valeurs absolues proches de un indiquent une corrélation élevée (comme pour la colonne "c"), tandis que des valeurs proches de zéro, au contraire, indiquent une distribution chaotique (colonne "a").
Balayage séquentiel
Pour compléter l'image, il faut noter qu'avec une condition non sélective, l'optimiseur aura raison de préférer le scan séquentiel de la table entière à l'utilisation de l'index:
postgres=# explain (costs off) select * from t where a <= 40000;
QUERY PLAN ------------------------ Seq Scan on t Filter: (a <= 40000) (2 rows)
Le fait est que les index fonctionnent mieux, plus la sélectivité de la condition est élevée, c'est-à -dire que moins de lignes y correspondent. La croissance du nombre de lignes récupérées augmente les frais généraux de lecture des pages d'index.
Les analyses séquentielles étant plus rapides que les analyses aléatoires aggravent la situation. Cela vaut particulièrement pour les disques durs, où l'opération mécanique consistant à amener une tête magnétique sur une piste prend beaucoup plus de temps que la lecture des données elle-même. Cet effet est moins visible pour les SSD. Deux paramètres sont disponibles pour prendre en compte les différences de coûts d'accès, "seq_page_cost" et "random_page_cost", que nous pouvons définir non seulement globalement, mais au niveau des tablespaces, de cette manière en ajustant les caractéristiques des différents sous-systèmes de disques.
Index de couverture
En règle générale, la tâche principale d'une méthode d'accès consiste à renvoyer les identificateurs des lignes de table correspondantes pour que le moteur d'indexation puisse lire les données nécessaires à partir de ces lignes. Mais que faire si l'index contient déjà toutes les données nécessaires à la requête? Un tel index est appelé
couverture , et dans ce cas, l'optimiseur peut appliquer l'
analyse d'index uniquement :
postgres=# vacuum t; postgres=# explain (costs off) select a from t where a < 100;
QUERY PLAN ------------------------------------ Index Only Scan using t_a_idx on t Index Cond: (a < 100) (2 rows)
Ce nom peut donner une idée que le moteur d'indexation n'accède pas du tout à la table et obtient toutes les informations nécessaires uniquement à partir de la méthode d'accès. Mais ce n'est pas exactement le cas puisque les index dans PostgreSQL ne stockent pas d'informations qui nous permettent de juger de la visibilité des lignes. Par conséquent, une méthode d'accès renvoie des versions de lignes qui correspondent à la condition de recherche quelle que soit leur visibilité dans la transaction en cours.
Cependant, si le moteur d'indexation devait examiner la table à des fins de visibilité à chaque fois, cette méthode d'analyse n'aurait pas été différente d'une analyse d'index standard.
Pour résoudre le problème, pour les tables, PostgreSQL maintient une soi-disant
carte de visibilité dans laquelle la mise à vide marque les pages où les données n'ont pas été modifiées suffisamment longtemps pour que ces données soient visibles par toutes les transactions indépendamment de l'heure de début et du niveau d'isolement. Si l'identifiant d'une ligne retournée par l'index se rapporte à une telle page, le contrôle de visibilité peut être évité.
Par conséquent, une aspiration régulière augmente l'efficacité des indices de couverture. De plus, l'optimiseur prend en compte le nombre de tuples morts et peut décider de ne pas utiliser l'analyse d'index uniquement s'il prévoit des frais généraux élevés pour le contrôle de visibilité.
Nous pouvons connaître le nombre d'accès forcés à une table à l'aide de la commande EXPLAIN ANALYZE:
postgres=# explain (analyze, costs off) select a from t where a < 100;
QUERY PLAN ------------------------------------------------------------------------------- Index Only Scan using t_a_idx on t (actual time=0.025..0.036 rows=99 loops=1) Index Cond: (a < 100) Heap Fetches: 0 Planning time: 0.092 ms Execution time: 0.059 ms (5 rows)
Dans ce cas, il n'était pas nécessaire d'accéder à la table (Heap Fetches: 0), car l'aspiration vient d'être effectuée. En général, plus ce nombre est proche de zéro, mieux c'est.
Tous les index ne stockent pas les valeurs indexées avec les identificateurs de ligne. Si la méthode d'accès ne peut pas renvoyer les données, elle ne peut pas être utilisée pour les analyses d'index uniquement.
PostgreSQL 11 a introduit une nouvelle fonctionnalité: INCLUDE-indexes. Que se passe-t-il s'il existe un index unique qui manque de colonnes pour être utilisé comme index de couverture pour une requête? Vous ne pouvez pas simplement ajouter les colonnes à l'index car cela romprait son caractère unique. La fonctionnalité permet d'inclure des colonnes non clés qui n'affectent pas l'unicité et ne peuvent pas être utilisées dans les prédicats de recherche, mais peuvent toujours servir aux analyses d'index uniquement. Le patch a été développé par ma collègue Anastasia Lubennikova.
Null
Les valeurs NULL jouent un rôle important dans les bases de données relationnelles en tant que moyen pratique de représenter une valeur inexistante ou inconnue.
Mais une valeur spéciale est spéciale à gérer. Une algèbre booléenne régulière devient ternaire; il n'est pas clair si NULL doit être plus petit ou plus grand que les valeurs normales (cela nécessite des constructions spéciales pour le tri, NULLS FIRST et NULLS LAST); il n'est pas évident si les fonctions d'agrégation doivent considérer les valeurs NULL ou non; une statistique spéciale est nécessaire pour le planificateur ...
Du point de vue de la prise en charge de l'index, il est également difficile de savoir si nous devons indexer ces valeurs ou non. Si les valeurs NULL ne sont pas indexées, l'index peut être plus compact. Mais si les valeurs NULL sont indexées, nous serons en mesure d'utiliser l'index pour des conditions telles que "
champ indexé IS [NOT] NULL" et également comme index de couverture lorsqu'aucune condition n'est spécifiée pour la table (car dans ce cas, le paramètre index doit renvoyer les données de toutes les lignes de table, y compris celles avec des valeurs NULL).
Pour chaque méthode d'accès, les développeurs décident individuellement d'indexer les NULL ou non. Mais en règle générale, ils sont indexés.
Index sur plusieurs domaines
Pour prendre en charge les conditions de plusieurs champs,
des index multicolonnes peuvent être utilisés. Par exemple, nous pouvons créer un index sur deux champs de notre table:
postgres=# create index on t(a,b); postgres=# analyze t;
L'optimiseur préférera très probablement cet index à la jonction de bitmaps car ici nous obtenons facilement les TID nécessaires sans aucune opération auxiliaire:
postgres=# explain (costs off) select * from t where a <= 100 and b = 'a';
QUERY PLAN ------------------------------------------------ Index Scan using t_a_b_idx on t Index Cond: ((a <= 100) AND (b = 'a'::text)) (2 rows)
Un index multicolonne peut également être utilisé pour accélérer la récupération des données par une condition pour certains des champs, en commençant par le premier:
postgres=# explain (costs off) select * from t where a <= 100;
QUERY PLAN -------------------------------------- Bitmap Heap Scan on t Recheck Cond: (a <= 100) -> Bitmap Index Scan on t_a_b_idx Index Cond: (a <= 100) (4 rows)
En général, si la condition n'est pas imposée au premier champ, l'index ne sera pas utilisé. Mais parfois, l'optimiseur peut considérer l'utilisation de l'index comme plus efficace que l'analyse séquentielle. Nous développerons ce sujet lors de l'examen des index "btree".
Toutes les méthodes d'accès ne prennent pas en charge la création d'index sur plusieurs colonnes.
Index sur les expressions
Nous avons déjà mentionné que la condition de recherche doit ressembler à une "
expression d'opérateur de champ indexé ". Dans l'exemple ci-dessous, l'index ne sera pas utilisé car une expression contenant le nom de champ est utilisée à la place du nom de champ lui-même:
postgres=# explain (costs off) select * from t where lower(b) = 'a';
QUERY PLAN ------------------------------------------ Seq Scan on t Filter: (lower((b)::text) = 'a'::text) (2 rows)
Il ne faut pas grand-chose pour réécrire cette requête spécifique afin que seul le nom du champ soit écrit à gauche de l'opérateur. Mais si cela n'est pas possible, les index sur les expressions (index fonctionnels) aideront:
postgres=# create index on t(lower(b)); postgres=# analyze t; postgres=# explain (costs off) select * from t where lower(b) = 'a';
QUERY PLAN ---------------------------------------------------- Bitmap Heap Scan on t Recheck Cond: (lower((b)::text) = 'a'::text) -> Bitmap Index Scan on t_lower_idx Index Cond: (lower((b)::text) = 'a'::text) (4 rows)
L'index fonctionnel est construit non pas sur un champ de table, mais sur une expression arbitraire. L'optimiseur considérera cet index pour des conditions telles que "
expression d'opérateur d'expression indexée ". Si le calcul de l'expression à indexer est une opération coûteuse, la mise à jour de l'index nécessitera également des ressources de calcul importantes.
Veuillez également garder à l'esprit qu'une statistique individuelle est collectée pour l'expression indexée. Nous pouvons connaître cette statistique dans la vue "pg_stats" par le nom de l'index:
postgres=# \dt
Table "public.t" Column | Type | Modifiers --------+---------+----------- a | integer | b | text | c | boolean | Indexes: "t_a_b_idx" btree (a, b) "t_a_idx" btree (a) "t_b_idx" btree (b) "t_lower_idx" btree (lower(b))
postgres=# select * from pg_stats where tablename = 't_lower_idx';
Il est possible, si nécessaire, de contrôler le nombre de paniers d'histogramme de la même manière que pour les champs de données normaux (en notant que le nom de la colonne peut différer selon l'expression indexée):
postgres=# \d t_lower_idx
Index "public.t_lower_idx" Column | Type | Definition --------+------+------------ lower | text | lower(b) btree, for table "public.t"
postgres=# alter index t_lower_idx alter column "lower" set statistics 69;
PostgreSQL 11 a introduit un moyen plus propre de contrôler la cible des statistiques pour les index en spécifiant le numéro de colonne dans la commande ALTER INDEX ... SET STATISTICS. Le patch a été développé par mon collègue Alexander Korotkov et Adrien Nayrat.
Index partiels
Parfois, il est nécessaire d'indexer uniquement une partie des lignes de table. Ceci est généralement lié à une distribution très non uniforme: il est logique de rechercher une valeur peu fréquente par un index, mais il est plus facile de trouver une valeur fréquente en parcourant complètement la table.
Nous pouvons certainement construire un index régulier sur la colonne «c», qui fonctionnera comme nous l'attendons:
postgres=# create index on t(c); postgres=# analyze t; postgres=# explain (costs off) select * from t where c;
QUERY PLAN ------------------------------- Index Scan using t_c_idx on t Index Cond: (c = true) Filter: c (3 rows)
postgres=# explain (costs off) select * from t where not c;
QUERY PLAN ------------------- Seq Scan on t Filter: (NOT c) (2 rows)
Et la taille de l'index est de 276 pages:
postgres=# select relpages from pg_class where relname='t_c_idx';
relpages ---------- 276 (1 row)
Mais comme la colonne «c» n'a la valeur true que pour 1% des lignes, 99% de l'index n'est en fait jamais utilisé. Dans ce cas, nous pouvons construire un index partiel:
postgres=# create index on t(c) where c; postgres=# analyze t;
La taille de l'index est réduite à 5 pages:
postgres=# select relpages from pg_class where relname='t_c_idx1';
relpages ---------- 5 (1 row)
Parfois, la différence de taille et de performances peut être assez importante.
Tri
Si une méthode d'accès renvoie des identificateurs de ligne dans un ordre particulier, cela fournit à l'optimiseur des options supplémentaires pour effectuer la requête.
Nous pouvons scanner le tableau puis trier les données:
postgres=# set enable_indexscan=off; postgres=# explain (costs off) select * from t order by a;
QUERY PLAN --------------------- Sort Sort Key: a -> Seq Scan on t (3 rows)
Mais nous pouvons lire les données en utilisant l'index facilement dans un ordre souhaité:
postgres=# set enable_indexscan=on; postgres=# explain (costs off) select * from t order by a;
QUERY PLAN ------------------------------- Index Scan using t_a_idx on t (1 row)
Seul «btree» sur toutes les méthodes d'accès peut renvoyer des données triées, alors reportons une discussion plus détaillée jusqu'à ce que nous considérions ce type d'index.
Bâtiment simultané
Habituellement, la construction d'un index acquiert un verrou SHARE pour la table. Ce verrou permet de lire les données de la table, mais interdit toute modification pendant la construction de l'index.
Nous pouvons nous en assurer si, par exemple, lors de la construction d'un index sur la table "t", nous effectuons la requĂŞte ci-dessous dans une autre session:
postgres=# select mode, granted from pg_locks where relation = 't'::regclass;
mode | granted -----------+--------- ShareLock | t (1 row)
Si la table est suffisamment grande et largement utilisée pour l'insertion, la mise à jour ou la suppression, cela peut sembler inadmissible car les processus de modification attendront une libération du verrou pendant longtemps.
Dans ce cas, nous pouvons utiliser la construction simultanée d'un index.
postgres=# create index concurrently on t(a);
Cette commande verrouille la table en mode SHARE UPDATE EXCLUSIVE, ce qui permet Ă la fois la lecture et la mise Ă jour (seule la modification de la structure de la table est interdite, ainsi que la mise sous vide, l'analyse ou la construction d'un autre index sur cette table).
Cependant, il y a aussi un revers. Tout d'abord, l'index sera construit plus lentement que d'habitude car deux passages à travers la table sont effectués au lieu d'un, et il est également nécessaire d'attendre la fin des transactions parallèles qui modifient les données.
Deuxièmement, avec la construction simultanée de l'index, un blocage peut se produire ou des contraintes uniques peuvent être violées. Cependant, l'index sera construit, bien qu'il ne fonctionne pas. Un tel index doit être supprimé et reconstruit. Les index non opérationnels sont marqués avec le mot INVALID dans la sortie de la commande psql \ d, et la requête ci-dessous renvoie une liste complète de ceux-ci:
postgres=# select indexrelid::regclass index_name, indrelid::regclass table_name from pg_index where not indisvalid;
index_name | table_name ------------+------------ t_a_idx | t (1 row)
Continuez Ă lire .