PostgreSQLLa base de données la plus sophistiquée au monde.

48.4. Considérations pour le verrouillage d'index

Une méthode d'accès aux index peut choisir si elle supporte les mises à jour concurrentes de l'index par plusieurs processus. Si le drapeau pg_am.amconcurrent de la méthode vaut true, alors le système principal PostgreSQL™ obtient AccessShareLock sur l'index lors d'un parcours d'index et RowExclusiveLock quand il le met à jour. Comme ces types de verrous ne sont pas en conflit, la méthode d'accès est responsable de la gestion d'un verrouillage plus précis si nécessaire. Un verrou exclusif sur l'index entier sera seulement pris lors de la création, destruction de l'index ou dans une opération REINDEX. Quand amconcurrent vaut false, PostgreSQL™ obtient toujours AccessShareLock lors des parcours d'index mais il obtient AccessExclusiveLock pour une mise à jour. Ceci nous assure que les processus mettant à jour ont l'utilisation exclusive de l'index. Notez que ceci assume implicitement que les parcours d'index sont en lecture seule ; une méthode d'accès qui pourrait modifier l'index lors d'un parcours devra toujours faire son propre verrouillage pour gérer les cas des parcours concurrents.

Rappelez-vous que les verrous d'un processus ne sont jamais en conflit ; du coup, même un type d'index non concurrent doit être préparé à gérer le cas où un moteur insère ou supprime des entrées dans un index qui est lui-même en train de parcourir (bien sûr, ceci est nécessaire pour supporter un UPDATE qui utilise l'index pour trouver les lignes à mettre à jour).

Construire un type d'index qui supporte les mises à jour concurrentes requiert une analyse complète et subtile du comportement requis. Pour les types d'index B-tree et hash, vous pouvez lire les décisions du concept dans src/backend/access/nbtree/README et src/backend/access/hash/README.

En plus des besoins de cohérence interne de l'index, les mises à jour concurrentes créent des problèmes de cohérence entre la table parent (l'en-tête) et l'index. Comme PostgreSQL™ sépare les accès et les mises à jour de l'en-tête de ceux de l'index, il existe des possibilités pour que l'index ne soit pas à jour par rapport à l'en-tête. Nous gérons ce problème avec les règles suivantes :

  • Une nouvelle entrée dans l'en-tête est effectuée avant sa contrepartie dans l'index (du coup, un parcours d'index concurrent pourrait ne pas voir l'entrée dans l'en-tête ; ceci est bon car le lecteur de l'index ne sera pas intéressé par une ligne non validée... voir Section 48.5, « Vérification de l'unicité de l'index »).

  • Quand une entrée de l'en-tête doit être supprimée (par VACUUM), toutes les entrées de l'index doivent d'abord être supprimées.

  • Pour des types d'index concurrents, un parcours d'index doit maintenir un lien sur la page d'index contenant le dernier élément renvoyé par amgettuple, et ambulkdelete ne peut pas supprimer les entrées de pages qui sont liées à d'autres processus. Le besoin de cette règle est expliqué plus bas.

Si un index est concurrent alors il est possible qu'un lecteur d'index voit une entrée dans l'index juste avant qu'elle ne soit supprimée par un VACUUM, puis d'arriver à l'entrée correspondante de l'en-tête après qu'elle soit supprimée par le VACUUM (avec un index non concurrent, ceci n'est pas possible à cause des verrous conflictuels au niveau index). Ceci ne crée pas de problèmes sérieux si ce numéro d'élément est toujours inutilisé quand le lecteur l'atteint car un emplacement d'élément vide sera ignoré par heap_fetch(). Mais que se passe-t'il si un troisième moteur a déjà ré-utilisé l'emplacement de l'élément pour quelque chose d'autre ? Lors de l'utilisation d'une image compatible MVCC, il n'y a pas de problème car le nouvel occupant de l'emplacement est certain d'être trop nouveau pour accepter ou renvoyer une ligne qui, en fait, ne correspond pas aux clés de parcours. Nous pouvons nous défendre contre ce scénario en réclamant que les clés de parcours soient de nouveau vérifiées avec la ligne d'en-tête dans tous les cas mais c'est bien trop coûteux. À la place, nous utilisons un lien sur une page d'index comme un proxy, pour indiquer que le lecteur pourrait toujours être « en parcours » à partir de l'entrée de l'index jusqu'à l'entrée correspondante. Faire bloquer ambulkdelete sur un tel lien nous assure qu'un VACUUM ne peut pas supprimer l'entrée de l'en-tête avant que le lecteur n'en ait terminé avec lui. Cette solution coûte peu en temps d'exécution mais ajoute un délai dans le blocage dans de rares cas où il existe réellement un conflit.

Cette solution requiert que les parcours d'index soient « synchrones » : nous devons récupérer chaque ligne d'en-tête immédiatement après avoir parcouru l'entrée d'index correspondante. Ceci est coûteux pour plusieurs raisons. Un parcours « asynchrone » dans lequel nous récupérons les TID de l'index et dans lequel nous visitons seulement les en-têtes de lignes un peu plus tard, requiert moins de temps de verrouillage de l'index et pourrait autoriser un modèle d'accès à l'en-tête plus efficace. En plus de l'analyse ci-dessus, nous devons utiliser l'approche synchronisée pour les images non compatibles avec MVCC mais un parcours asynchrone est possible pour une requête utilisant une image MVCC.

Dans un parcours d'index amgetmulti, la méthode d'accès n'a pas besoin de garantir la conservation d'un lien à l'index sur aucune des lignes renvoyées, ce qui est impraticable pour toutes sauf la dernière). Du coup, il est plus sage d'utiliser plusieurs parcours avec des images compatibles MVCC.