Il est très difficile d'implémenter des règles de gestion sur l'intégrité des données en utilisant des transactions Read Committed parce que la vue des données est changeante avec chaque ordre, et même un seul ordre peut ne pas se cantonner à son propre instantané si un conflit en écriture se produit.
Bien qu'une transaction Repeatable Read ait une vue stable des données dans toute la durée de son exécution, il y a un problème subtil quand on utilise les instantanés MVCC pour vérifier la cohérence des données, impliquant quelque chose connu sous le nom de conflits lecture/écriture. Si une transaction écrit des données et qu'une transaction concurrente essaie de lire la même donnée (que ce soit avant ou après l'écriture), elle ne peut pas voir le travail de l'autre transaction. Le lecteur donne donc l'impression de s'être exécuté le premier, quel que soit celui qui a commencé le premier ou qui a validé le premier. Si on s'en tient là, ce n'est pas un problème, mais si le lecteur écrit aussi des données qui sont lues par une transaction concurrente, il y a maintenant une transaction qui semble s'être exécutée avant les transactions précédemment mentionnées. Si la transaction qui semble s'être exécutée en dernier valide en premier, il est très facile qu'un cycle apparaisse dans l'ordre d'exécution des transactions. Quand un cycle de ce genre apparaît, les contrôles d'intégrité ne fonctionneront pas correctement sans aide.
Comme mentionné dans Section 13.2.3, les transactions Serializable ne sont que des transactions Repeatable Read qui ajoutent une supervision non bloquante de formes dangereuses de conflits lecture/écriture. Quand une de ces formes est détectée qui pourrait entraîner un cycle dans l'ordre apparent d'exécution, une des transactions impliquées est annulée pour casser le cycle.
Si le niveau d'isolation de transactions Serializable est utilisé pour toutes les écritures et toutes les lectures qui ont besoin d'une vue cohérente des données, aucun autre effort n'est requis pour garantir la cohérence. Un logiciel d'un autre environnement écrit pour utiliser des transactions Serializable pour garantir la cohérence devrait « fonctionner sans modification » de ce point de vue dans PostgreSQL.
L'utilisation de cette technique évitera de créer une
charge de travail inutile aux développeurs d'applications
si le logiciel utilise un framework qui réessaie automatiquement
les transactions annulées pour échec de sérialisation.
Cela pourrait être une bonne idée de
positionner default_transaction_isolation
à
serializable
. Il serait sage, par ailleurs, de
vous assurer qu'aucun autre niveau d'isolation n'est utilisé, soit
par inadvertance, soit pour contourner les contrôles d'intégrité,
en vérifiant les niveaux d'isolations dans les triggers.
Voyez Section 13.2.3 pour des suggestions sur les performances.
Ce niveau de protection de l'intégrité en utilisant des transactions Serializable ne s'étend pour le moment pas jusqu'au mode standby (Section 27.4) ou aux réplicas logiques. Pour cette raison, les utilisateurs du hot standby ou de réplication logique voudront peut-être utiliser le niveau Repeatable Read et un verrouillage explicite sur le primaire.
Quand des écritures non sérialisables sont possibles, pour garantir
la validité courante d'un enregistrement et le protéger contre
des mises à jour concurrentes, on doit utiliser SELECT
FOR UPDATE
, SELECT FOR SHARE
, ou un
ordre LOCK TABLE
approprié. (SELECT
FOR UPDATE
et SELECT FOR SELECT
ne
verrouillent que les lignes retournées contre les mises à jour
concurrentes, tandis que LOCK TABLE
verrouille
toute la table.) Cela doit être pris en considération quand
vous portez des applications PostgreSQL
à partir d'autres environnements.
Il est aussi important de noter pour ceux qui convertissent à
partir d'autres environnements le fait que SELECT
FOR UPDATE
ne garantit pas qu'une transaction
concurrente ne mettra pas à jour ou n'effacera
pas l'enregistrement sélectionné. Pour faire cela dans
PostgreSQL, vous devez réellement modifier
l'enregistrement, même si vous n'avez pas besoin de modifier une
valeur. SELECT FOR UPDATE
empêche
temporairement les autres transactions d'acquérir
le même verrou ou d'exécuter un UPDATE
ou DELETE
qui modifierait l'enregistrement
verrouillé, mais une fois que la transaction possédant ce verrou
valide ou annule, une transaction bloquée pourra continuer avec son
opération en conflit, sauf si un réel UPDATE
de l'enregistrement a été effectué pendant que le verrou était
possédé.
Les vérifications globales de validité demandent davantage de
réflexion sous un MVCC non sérialisable. Par
exemple, une application bancaire pourrait vouloir vérifier que
la somme de tous les crédits d'une table est égale à la somme
de tous les débits d'une autre, alors que les deux tables sont
en cours de mise à jour. La comparaison des résultats de deux
SELECT sum(...)
successifs ne fonctionnera pas
correctement en mode Read Committed, puisque la seconde requête
inclura probablement les résultats de transactions non prises en
compte dans la première. Effectuer les deux sommes dans une seule
transaction repeatable read donnera uniquement une image précise des effets
des transactions qui ont validé avant le début de la
transaction repeatable read — mais on pourrait légitimement
se demander si la réponse est toujours valide au moment où elle
est fournie. Si la transaction repeatable read a elle-même effectué
des modifications avant d'effectuer le test de cohérence, l'utilité
de la vérification devient encore plus sujette à caution, puisque
maintenant elle inclut des modifications depuis le début de la
transaction, mais pas toutes. Dans ce genre de cas, une personne
prudente pourra vouloir verrouiller toutes les tables nécessaires
à la vérification, afin d'avoir une vision incontestable de
la réalité courante. Un mode SHARE
(ou plus
élevé) garantit qu'il n'y a pas de changements non validés dans
la table verrouillée, autres que ceux de la transaction courante.
Notez aussi que si on se fie au verrouillage explicite pour
empêcher les mises à jour concurrentes, on devrait soit utiliser
Read Committed, soit utiliser Repeatable Read et faire attention
à obtenir les verrous avant d'effectuer les requêtes. Un verrou
obtenu par une transaction repeatable read garantit qu'aucune autre
transaction modifiant la table n'est en cours d'exécution, mais si
l'instantané vu par la transaction est antérieur à l'obtention du
verrou, il pourrait aussi précéder des modifications maintenant
validées dans la table. Un instantané de transaction repeatable
read est en fait figé à l'exécution de sa première requête ou
commande de modification de données (SELECT
,
INSERT
, UPDATE
, ou
DELETE
), il est donc possible d'obtenir les
verrous explicitement avant que l'instantané ne soit figé.
Le niveau d'isolation Serializable est implémenté en utilisant une technique connue dans la littérature académique sur les bases de données sous le nom de Serializable Snapshot Isolation, qui ajoute à une isolation de snapshot en ajoutant des vérifications sur les anomalies de sérialisation. Quelques différences dans le comportement et les performances sont observables si on le compare à d'autres systèmes qui utilisent une technique de verrouillage traditionnelle. Merci de lire [ports12] pour des informations détaillées.