Les méthodes d'accès aux index doivent gérer des mises à jour
concurrentes de l'index par plusieurs processus.
Le système principal de PostgreSQL obtient
AccessShareLock
sur l'index lors d'un parcours d'index et
RowExclusiveLock
lors de sa mise à jour (ce qui inclut le
VACUUM
simple). Comme ces types de
verrous ne sont pas conflictuels, la méthode d'accès est responsable de la
finesse du verrouillage dont elle a besoin. Un verrou de type
ACCESS EXCLUSIVE
sur l'intégralité de l'index entier
n'est posé qu'à la création de l'index, sa destruction ou lors d'un
REINDEX
(un verrou SHARE UPDATE
EXCLUSIVE
est pris à la place avec l'option
CONCURRENTLY
).
Construire un type d'index qui supporte les mises à jour concurrentes
requiert une analyse complète et subtile. Pour les
types d'index B-tree et hash, on peut lire les implications sur les décisions
de conception 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 parente (heap) et l'index. Comme PostgreSQL sépare les accès et les mises à jour de la table et ceux de l'index, il existe des fenêtres temporelles pendant lesquelles l'index et l'en-tête peuvent être incohérents. Ce problème est géré avec les règles suivantes :
une nouvelle entrée dans la table est effectuée avant son entrée dans l'index. (Un parcours d'index concurrent peut alors ne pas voir l'entrée dans la table. Ce n'est pas gênant dans la mesure où un lecteur de l'index ne s'intéresse pas à une ligne non validée. Voir Section 64.5) ;
lorsqu'une entrée de la table va être supprimée (par VACUUM
),
on doit d'abord supprimer toutes les entrées d'index ;
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
supprimer des entrées de pages liées à d'autres processus. La
raison figure ci-dessous.
Sans la troisième règle, il serait possible qu'un lecteur d'index voit
une entrée dans l'index juste avant qu'elle ne soit supprimée par un
VACUUM
et arrive à l'entrée correspondante de
la table après sa suppression par le VACUUM
.
Cela ne pose aucun problème sérieux si cet élément
est toujours inutilisé quand le lecteur l'atteint, car tout emplacement
vide est 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'un instantané (snapshot) compatible MVCC, il n'y
a pas de problème car le nouvel occupant de l'emplacement est certain d'être
trop récent pour apparaître dans l'instantané.
En revanche, avec un
instantané non-compatible MVCC (tel que SnapshotAny
), une
ligne qui ne correspond pas aux clés de parcours peut être acceptée ou
retournée. Ce scénario peut être évité en imposant que les clés de parcours
soient re-confrontées à la table dans tous les cas, mais cela est
trop coûteux. À la place, un lien sur une page d'index est utilisé comme
proxy pour indiquer que le
lecteur peut être « en route » depuis l'entrée
d'index vers l'entrée de table correspondante. Bloquer ambulkdelete
sur un tel lien assure que VACUUM
ne peut pas supprimer
l'entrée de la table avant que le lecteur n'en ait terminé avec elle. Cette
solution est peu coûteuse en temps d'exécution, et n'ajoute de surcharge du
fait du blocage que dans les rares cas où il y a vraiment un conflit.
Cette solution requiert que les parcours d'index soient « synchrones » : chaque ligne de la table doit être récupérée immédiatement après récupération de l'entrée d'index correspondante. Cela est coûteux pour plusieurs raisons. Un parcours « asynchrone », où l'on récupère de nombreux TID depuis l'index et où l'on ne visite la table que plus tard, requiert moins de surcharge de verrouillage de l'index et autorise un modèle d'accès à la table plus efficace. D'après l'analyse ci-dessus, l'approche synchrone doit être utilisée pour les instantanés non compatibles avec MVCC, mais un parcours asynchrone est possible pour une requête utilisant un instantané MVCC.
Dans un parcours d'index amgetbitmap
, la méthode d'accès
ne bloque l'index pour aucune des lignes
renvoyées. C'est pourquoi de tels parcours ne sont fiables
qu'avec les instantanés compatibles MVCC.
Quand le drapeau ampredlocks
n'est pas en place,
tout parcours par cette méthode d'accès au sein d'une transaction
sérialisable acquerra un verrou prédicat non bloquant sur l'index
complet. Ceci génèrera un conflit de lecture/écriture à l'insertion d'une
ligne dans cet index par une transaction sérialisable concurrente. Si
certains motifs de tels conflits sont détectés dans un
ensemble de transactions sérialisables concurrentes, une de ces transactions
peut être annulée pour protéger l'intégrité des données. Quand le
drapeau est en place, il indique que la méthode d'accès
implémente un verrou prédicat plus fin, qui tend à réduire la fréquence
d'annulation de telles requêtes.