PostgreSQL™ fournit de nombreux modes de verrous pour contrôler les accès simultanés aux données des tables. Ces modes peuvent être utilisés pour contrôler le verrouillage par l'application dans des situations où MVCC n'a pas le comportement désiré. De plus, la plupart des commandes PostgreSQL™ acquièrent automatiquement des verrous avec les modes appropriés pour s'assurer que les tables référencées ne sont pas supprimées ou modifiées de façon incompatible lorsque la commande s'exécute (par exemple, ALTER TABLE ne peut pas être exécuté de façon sûr en même temps que d'autres opérations sur la même table, donc il obtient un verrou exclusif sur la table pour s'assurer d'une bonne exécution).
Pour examiner une liste des verrous en cours, utilisez la vue système pg_locks. Pour plus d'informations sur la surveillance du statut du sous-système de gestion des verrous, référez-vous au Chapitre 27, Surveiller l'activité de la base de données.
La liste ci-dessous affiche les modes de verrous disponibles et les contextes dans lesquels ils sont automatiquement utilisés par PostgreSQL™. Vous pouvez aussi acquérir explicitement n'importe lequel de ces verrous avec la commande LOCK(7). Rappelez-vous que tous ces modes de verrous sont des verrous au niveau table, même si le nom contient le mot « row » (NdT : ligne) ; les noms des modes de verrous sont historiques. Dans une certaine mesure, les noms reflètent l'utilisation typique de chaque mode de verrou -- mais la sémantique est identique. La seule vraie différence entre un mode verrou et un autre est l'ensemble des modes verrous avec lesquels ils rentrent en conflit (voir Tableau 13.2, « Modes de verrou conflictuels »). Deux transactions ne peuvent pas conserver des verrous de modes en conflit sur la même table au même moment (néanmoins, une transaction n'entre jamais en conflit avec elle-même. Par exemple, elle pourrait acquérir un verrou ACCESS EXCLUSIVE et acquérir plus tard un verrou ACCESS SHARE sur la même table). Des modes de verrou sans conflit peuvent être détenus en même temps par plusieurs transactions. Notez, en particulier, que certains modes de verrous sont en conflit avec eux-même (par exemple, un verrou ACCESS EXCLUSIVE ne peut pas être détenu par plus d'une transaction à la fois) alors que d'autres n'entrent pas en conflit avec eux-même (par exemple, un verrou ACCESS SHARE peut être détenu par plusieurs transactions).
Modes de verrous au niveau table
En conflit avec le mode verrou ACCESS EXCLUSIVE.
Les commandes SELECT acquièrent un verrou de ce mode avec les tables référencées. En général, tout requête lisant seulement une table et ne la modifiant pas obtient ce mode de verrou.
En conflit avec les modes de verrous EXCLUSIVE et ACCESS EXCLUSIVE.
La commande SELECT FOR UPDATE et SELECT FOR SHARE acquièrent un verrou de ce mode avec la table cible (en plus des verrous ACCESS SHARE des autres tables référencées mais pas sélectionnées FOR UPDATE/FOR SHARE).
En conflit avec les modes de verrous SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE.
Les commandes UPDATE, DELETE et INSERT acquièrent ce mode de verrou sur la table cible (en plus des verrous ACCESS SHARE sur toutes les autres tables référencées). En général, ce mode de verrouillage sera acquis par toute commande modifiant des données de la table.
En conflit avec les modes de verrous SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE. Ce mode protège une table contre les modifications simultanées de schéma et l'exécution d'un VACUUM.
Acquis par VACUUM (sans FULL), ANALYZE et CREATE INDEX CONCURRENTLY.
En conflit avec les modes de verrous ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE. Ce mode protège une table contre les modifications simultanées des données.
Acquis par CREATE INDEX (sans CONCURRENTLY).
En conflit avec les modes de verrous ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE.
Ce mode de verrouillage n'est automatiquement acquis par aucune commande PostgreSQL™.
En conflit avec les modes de verrous ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE. Ce mode autorise uniquement les verrous ACCESS SHARE concurrents, c'est-à-dire que seules les lectures à partir de la table peuvent être effectuées en parallèle avec une transaction contenant ce mode de verrouillage.
Ce mode de verrouillage n'est pas automatiquement acquis sur les tables utilisateur par une commande PostgreSQL™. Néanmoins, il est utilisé sur certains catalogues systèmes pour certaines opérations.
Entre en conflit avec tous les modes (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE et ACCESS EXCLUSIVE). Ce mode garantit que le détenteur est la seule transaction à accéder à la table de quelque façon que ce soit.
Acquis par les commandes ALTER TABLE, DROP TABLE, TRUNCATE, REINDEX, CLUSTER et VACUUM FULL. C'est aussi le mode de verrou par défaut des instructions LOCK TABLE qui ne spécifient pas explicitement de mode de verrouillage.
Seul un verrou ACCESS EXCLUSIVE bloque une instruction SELECT (sans FOR UPDATE/SHARE).
Une fois acquis, un verrou est normalement détenu jusqu'à la fin de la transaction. Mais si un verrou est acquis après l'établissement d'un point de sauvegarde, le verrou est relâché immédiatement si le point de sauvegarde est annulé. Ceci est cohérent avec le principe du ROLLBACK annulant tous les effets des commandes depuis le dernier point de sauvegarde. Il se passe la même chose pour les verrous acquis à l'intérieur d'un bloc d'exception PL/pgSQL : un échappement d'erreur à partir du bloc lâche les verrous acquis dans le bloc.
Tableau 13.2. Modes de verrou conflictuels
Verrou demandé | Verrou déjà détenu | |||||||
---|---|---|---|---|---|---|---|---|
ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE | |
ACCESS SHARE | X | |||||||
ROW SHARE | X | X | ||||||
ROW EXCLUSIVE | X | X | X | X | ||||
SHARE UPDATE EXCLUSIVE | X | X | X | X | X | |||
SHARE | X | X | X | X | X | |||
SHARE ROW EXCLUSIVE | X | X | X | X | X | X | ||
EXCLUSIVE | X | X | X | X | X | X | X | |
ACCESS EXCLUSIVE | X | X | X | X | X | X | X | X |
En plus des verrous au niveau table, il existe des verrous au niveau ligne, qui peuvent être des verrous exclusifs ou partagés. Un verrou exclusif sur une ligne spécifique est automatiquement acquis lorsque la ligne est mise à jour ou supprimée. Le verrou est détenu jusqu'à la fin de la transaction, que ce soit une validation ou une annulation, de la même façon que les verrous de niveau table. Les verrous au niveau ligne n'affectent pas les requêtes sur les données ; ils bloquent seulement les modifieurs d'une même ligne.
Pour acquérir un verrou exclusif au niveau ligne sans modifier réellement la ligne, sélectionnez la ligne avec SELECT FOR UPDATE. Notez qu'une fois le verrou au niveau ligne acquis, la transaction pourrait mettre à jour la ligne plusieurs fois sans peur des conflits.
Pour acquérir un verrou partagé niveau ligne sur une ligne spécifique, sélectionnez la ligne avec SELECT FOR SHARE. Un verrou partagé n'empêche pas les autres transactions d'obtenir le même verrou partagé. Néanmoins, aucune transaction n'est autorisée à mettre à jour, supprimer ou verrouiller exclusivement une ligne dont une autre transaction a obtenu un verrou partagé. Toute tentative de le faire bloque tant que les verrous partagés n'ont pas été enlevés.
PostgreSQL™ ne garde en mémoire aucune information sur les lignes modifiées, il n'y a donc aucune limite sur le nombre de lignes verrouillées à un moment donné. Néanmoins, verrouiller une ligne peut causer une écriture disque ; ainsi, par exemple, SELECT FOR UPDATE modifie les lignes sélectionnées pour les marquer verrouillées et cela aboutit à des écritures disques.
En plus des verrous tables et lignes, les verrous partagés/exclusifs sur les pages sont utilisés pour contrôler la lecture et l'écriture des pages de table dans l'ensemble des tampons partagées. Ces verrous sont immédiatement relâchés une fois la ligne récupérée ou mise à jour. Les développeurs d'applications ne sont normalement pas concernés par les verrous au niveau page mais nous les mentionnons dans un souci d'exhaustivité.
L'utilisation de verrous explicites accroît le risque de verrous morts lorsque deux transactions (voire plus) détiennent chacune un verrou que l'autre convoite. Par exemple, si la transaction 1 a acquis un verrou exclusif sur la table A puis essaie d'acquérir un verrou exclusif sur la table B alors que la transaction 2 possède déjà un verrou exclusif sur la table B et souhaite maintenant un verrou exclusif sur la table A, alors aucun des deux ne peut continuer. PostgreSQL™ détecte automatiquement ces situations de blocage et les résout en annulant une des transactions impliquées, permettant ainsi à l'autre (aux autres) de se terminer (quelle est exactement la transaction annulée est difficile à prévoir mais vous ne devriez pas vous en préoccuper).
Notez que les verrous morts peuvent aussi se produire en conséquence à des verrous de niveau ligne (et du coup, ils peuvent se produire même si le verrouillage explicite n'est pas utilisé). Considérons le cas où il existe deux transactions concurrentes modifiant une table. La première transaction exécute :
UPDATE comptes SET balance = balance + 100.00 WHERE no_compte = 11111;
Elle acquiert un verrou au niveau ligne sur la ligne spécifiée par le numéro de compte (no_compte). Ensuite, la deuxième transaction exécute :
UPDATE comptes SET balance = balance + 100.00 WHERE no_compte = 22222; UPDATE comptes SET balance = balance - 100.00 WHERE no_compte = 11111;
La première instruction UPDATE acquiert avec succès un verrou au niveau ligne sur la ligne spécifiée, donc elle réussit à mettre à jour la ligne. Néanmoins, la deuxième instruction UPDATE trouve que la ligne qu'elle essaie de mettre à jour a déjà été verrouillée, alors elle attend la fin de la transaction ayant acquis le verrou. Maintenant, la première transaction exécute :
UPDATE comptes SET balance = balance - 100.00 WHERE no_compte = 22222;
La première transaction essaie d'acquérir un verrou au niveau ligne sur la ligne spécifiée mais ne le peut pas : la deuxième transaction détient déjà un verrou. Donc, elle attend la fin de la transaction deux. Du coup, la première transaction est bloquée par la deuxième et la deuxième est bloquée par la première : une condition de blocage, un verrou mort. PostgreSQL™ détectera cette situation et annulera une des transactions.
La meilleure défense contre les verrous morts est généralement de les éviter en s'assurant que toutes les applications utilisant une base de données acquièrent des verrous sur des objets multiples dans un ordre cohérent. Dans l'exemple ci-dessus, si les deux transactions avaient mis à jour les lignes dans le même ordre, aucun blocage n'aurait eu lieu. Vous devez vous assurer que le premier verrou acquis sur un objet dans une transaction est dans le mode le plus restrictif pour cet objet. S'il n'est pas possible de vérifier ceci à l'avance, alors les blocages doivent être gérés à l'exécution en ré-essayant les transactions annulées à cause de blocage.
Tant qu'aucune situation de blocage n'est détectée, une transaction cherchant soit un verrou de niveau table soit un verrou de niveau ligne attend indéfiniment que les verrous en conflit soient relâchés. Ceci signifie que maintenir des transactions ouvertes sur une longue période de temps (par exemple en attendant une saisie de l'utilisateur) est parfois une mauvaise idée.
PostgreSQL™ fournit un moyen pour créer des verrous qui ont une signification définie par l'application. Ils sont qualifiés d'informatifs car le système ne force pas leur utilisation -- c'est à l'application de les utiliser correctement. Les verrous informatifs peuvent être utiles pour des manières d'utiliser le verrouillage qui ne sont pas en phase avec le modèle MVCC. Une fois acquis, un verrou informatif est détenu jusqu'à ce qu'il soit relâché ou que la session se termine. Contrairement aux verrous standards, les verrous informatifs n'honorent pas la sémantique des transactions : un verrou informatif acquis lors d'une transaction qui est par la suite annulée est toujours détenu après l'annulation. Et de la même façon un déverrouillage sera toujours effectif même si la transaction appelante échoue ultérieurement. Le même verrou peut être acquis plusieurs fois par le processus qui le détient : à chaque demande de verrou doit correspondre une demande de déverrouillage afin que le verrou soit réellement supprimé. (Si une session détient déjà un verrou donné, les demandes suivantes réussissent même si d'autres sessions attendent le verrou.) Comme tous les verrous de PostgreSQL™, une liste complète des verrous informatifs détenus actuellement par une session est visible dans la vue système pg_locks.
Les verrous informatifs sont alloués à partir d'une portion de mémoire partagée dont la taille est définie par les variables de configuration max_locks_per_transaction et max_connections. Attention à ne pas vider cette mémoire, sinon le serveur ne serait plus capable d'accorder des verrous. Ceci impose une limite supérieure au nombre de verrous informatifs que le serveur peut accorder, typiquement entre des dizaines et des centaines de milliers suivant la façon dont le serveur est configuré.
Une utilisation commune des verrous informatifs est d'émuler des stratégies de verrou pessimiste, typiques des systèmes de gestion de données sur « fichier plat ». Alors qu'une option stockée dans une table pourrait être utilisée dans le même but, les verrous informatifs sont plus rapides, évitent le grossissement de MVCC et sont automatiquement nettoyés par le serveur à la fin d'une session. Dans certains cas qui utilisent cette méthode, tout spécialement les requêtes impliquant un tri explicite et des clauses LIMIT, une grande attention doit être portée au contrôle des verrous acquis, à cause de l'ordre dans lequel les expressions SQL sont évaluées. Par exemple :
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger ! SELECT pg_advisory_lock(q.id) FROM ( SELECT id FROM foo WHERE id > 12345 LIMIT 100 ) q; -- ok
Dans les requêtes ci-dessus, la deuxième forme est dangereuse parce qu'il n'est pas garanti que l'application de LIMIT ait lieu avant que la fonction du verrou soit exécutée. Ceci pourrait entraîner l'acquisition de certains verrous que l'application n'attendait pas, donc qu'elle ne pourrait, du coup, pas relâcher (sauf à la fin de la session). Du point de vue de l'application, de tels verrous sont en attente, bien qu'ils soient visibles dans pg_locks.
Les fonctions fournies pour manipuler les verrous informatifs sont décrites dans Tableau 9.61, « Fonctions de verrous consultatifs ».