PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 10.23 » Langage SQL » Index » Parcours d'index seul

11.11. Parcours d'index seul

Tous les index dans PostgreSQL sont des index secondaires, ceci signifiant que chaque index est stocké séparément des données de la table (ce qui est appelé le heap dans la terminologie PostgreSQL). Ceci signifie que, dans un parcours d'index habituel, chaque récupération de ligne nécessite de récuperer les données de l'index et du heap. De plus, bien que les entrées d'un index correspondant à une condition WHERE indexable sont habituellement proches dans l'index, les lignes de la table qu'elles référencent peuvent se trouver n'importe où dans le heap. La portion accédée du heap pendant un parcours d'index implique donc beaucoup d'accès aléatoire au heap, ce qui peut être lent, tout particulièrement sur les disques magnétiques traditionnels. (Comme décrit dans Section 11.5, les parcours de bitmap essaient de diminuer ce coût en réalisant les accès au heap de façon ordonnée, mais cette méthode a ces limites.)

Pour résoudre ce problème de performance, PostgreSQL supporte les parcours d'index seul, qui peuvent répondre aux requêtes à partir d'un index seul sans aucun accès au heap. L'idée de base est de renvoyer les valeurs directement à partir de chaque entrée dans l'index au lieu de consulter l'entrée associée dans le heap. Il existe deux restrictions fondamentales pour l'utilisation de cette méthode :

  1. Le type d'index doit supporter les parcours d'index seul. Les index B-tree peuvent toujours le faire. Les index GiST et SP-GiST supportent les parcours d'index seul uniquement pour certaines classes d'opérateur, mais pas pour les autres. D'autres types d'index n'ont aucun support. Le pré-requis sous-jacent est que l'index doit enregistrer physiquement, ou être capable de reconstruire, les données originales pour chaque entrée d'index. En contre exemple, les index GIN ne supportent pas les parcours d'index seul car chaque entrée d'index contient typiquement seulement une partie de la valeur originale.

  2. La requête doit référencer seulement les colonnes enregistrées dans l'index. Par exemple, avec un index sur les colonnes x et y d'une table qui a aussi une colonne z, ces requêtes peuvent utiliser des parcours d'index seul :

    SELECT x, y FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND y < 42;
         

    alors que ces requêtes ne le peuvent pas :

    SELECT x, z FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND z < 42;
         

    (Les index fonctionnels et les index partiels compliquent cette règle, comme expliqué ci-dessous.)

Si ces deux pré-requis fondamentaux sont rencontrés, alors toutes les valeurs requises par la requête sont disponibles dans l'index, donc un parcours d'index seul est physiquement possible. Mais il existe un pré-requis supplémentaire pour tout parcours de table dans PostgreSQL : il doit vérifier que chaque ligne récupérée soit « visible » dans le cadre du snapshot MVCC de la requête, comme indiqué dans Chapitre 13. Les informations de visibilité ne sont pas enregistrées dans les entrées de l'index, uniquement dans les entrées de la table. Donc a priori, cela voudrait dire que chaque récupération de ligne nécessite un accès au heap la table. Et c'est bien le cas si la ligne de la table a été modifiée récemment. Néanmoins, pour les données changeant peu, il y a toujours un moyen de contourner ce problème. PostgreSQL trace pour chaque page dans le heap de la table, si toutes les lignes enregistrées dans cette page sont suffisamment anciennes pour être visibles par toutes les transactions en cours et futures. Cette information est enregistrée dans un bit de la carte de visibilité de la table. Un parcours d'index seul, pour trouver une entrée d'index candidate, vérifie le bit de la carte de visibilité pour la page correspondante du heap. Si ce bit est vrai, la ligne est connue comme étant visible et donc la donnée peut être renvoyer sans plus de tests. Dans le cas contraire, l'entrée heap doit être visitée pour trouver si elle est visible, donc aucune amélioration des performances n'est obtenue par rapport à un parcours d'index standard. Même dans le cas d'une réussite, cette approche remplace des accès au heap par des accès à la carte de visibilité. Comme la carte de visibilité est quatre fois plus petite que le heap qu'elle décrit, moins d'accès IO sont nécessaires pour accéder à l'information. Dans la plupart des cas, la carte de visibilité reste en mémoire tout le temps.

En bref, quand un parcours d'index seul est possible d'après les deux pré-requis fondamentaux, son utilisation ne sera réellement intéressante que si une faction significative des blocs du heap de la table ont leur bit all-visible configuré. Mais les tables dont une large fraction des lignes ne changent pas sont suffisamment habituellement pour que ce type de parcours se révèle très utile en pratique.

Pour une utilisation efficace de la fonctionnalité du parcours d'index seul, vous pouvez choisir de créer les index dans lesquels seuls les premières colonnes auront une importante pour les correspondances avec les clauses WHERE, alors que les colonnes en fin sont des données supplémentaires (« payload ») pouvant être utilisées pour répondre à la requête. Par exemple, si vous exécutez fréquemment des requêtes comme :

SELECT y FROM tab WHERE x = 'key';
  

l'approche habituelle pour accélérer de telles requêtes est de créer un index uniquement sur x. Néanmoins, un index sur (x, y) offrira la possibilité d'exécuter cette requête avec un parcours d'index seul. Comme demandé précédemment, un tel index serait plus volumineux et donc plus efficace qu'un index uniquement sur x, donc c'est intéressant seulement si la table est connue pour être principalement statique. Notez qu'il est important que l'index soit déclarée sur (x, y), et non pas sur (y, x), car pour la plupart des types d'index (et tout particulièrement les B-tree), les recherches qui ne concernent pas les premières colonnes de l'index ne sont pas très efficaces.

En principe, les parcours d'index seul peuvent être utilisés avec des index fonctionnels. Par exemple, avec un index sur f(x) est une colonne de table, il est possible de l'utiliser avec la requête suivante :

SELECT f(x) FROM tab WHERE f(x) < 1;
  

pour un parcours d'index seul ; et c'est très intéressant si f() est une fonction coûteuse à l'exécution. Néanmoins, l'optimiseur de PostgreSQL n'est pas très intelligent actuellement avec de tels cas. Il considère qu'une requête est réalisable avec un parcours d'index seul seulement quand toutes les colonnes nécessaires pour la requête sont disponibles à partir de l'index. Dans cet exemple, x n'est pas nécessaire, sauf dans le contexte f(x), mais le planificateur ne le remarque pas et conclut qu'un parcours d'index seul n'est pas possible. Si un parcours d'index seul semble suffisamment intéressante, ceci peut être contourner en déclarant l'index sur (f(x), x), où on ne s'attend pas à ce que la deuxième colonne soit utilisée en pratique mais qu'elle est seulement là pour convaincre l'optimiseur qu'un parcours d'index seul est possible. Si le but est d'éviter de recalculer f(x), une autre astuce est que l'optimiseur ne fera pas forcément une correspondance entre les utilisations de f(x) qui ne sont pas dans les clauses WHERE indexables et la colonne de l'index. Généralement, le test sera efficace pour les requêtes simples comme indiquées ci-dessus mais par pour les requêtes qui impliquent des jointures. Ces déficiences pourraient être corrigées dans les versions futures de PostgreSQL.

Les index partiels ont aussi des interactions intéressantes avec les parcours d'index seul. Considérez l'index partiel indiqué dans Exemple 11.3 :

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;
  

En principe, nous pouvons faire un parcours d'index seul sur cet index pour satisfaire une requête du type :

SELECT target FROM tests WHERE subject = 'some-subject' AND success;
  

Mais il reste un problème : la clause WHERE fait référence à success qui n'est pas disponible comme colonne de résultat de l'index. Néanmoins, un parcours d'index seul est possible parce que le plan n'a pas besoin de vérifier de nouveau cette partie de la clause WHERE à l'exécution : toutes les entrées trouvées dans l'index ont obligatoirement success = true, donc il n'est pas nécessaire de le vérifier explicitement dans le plan. Les versions 9.6 et ultérieures de PostgreSQL reconnaîtront de tels cas et permettront aux parcours d'index seul d'être générés, mais les anciennes versions ne le pourront pas.