Mat et Erwin ont raison, et j'ajoute une autre réponse pour développer davantage ce qu'ils ont dit d'une manière qui ne rentre pas dans un commentaire. Comme leurs réponses ne semblent pas satisfaire tout le monde, et il a été suggéré que les développeurs de PostgreSQL soient consultés, et j'en suis un, je vais élaborer.
Le point important ici est qu’en vertu de la norme SQL, dans une transaction exécutée au READ COMMITTED
niveau d’isolation de transaction, la restriction est que le travail des transactions non validées ne doit pas être visible. Lorsque le travail des transactions validées devient visible dépend de la mise en œuvre. Ce que vous soulignez est une différence dans la façon dont deux produits ont choisi de mettre en œuvre cela. Aucune de ces implémentations ne viole les exigences de la norme.
Voici ce qui se passe dans PostgreSQL, en détail:
S1-1 fonctionne (1 ligne supprimée)
L'ancienne ligne est laissée en place, car S1 peut toujours revenir en arrière, mais S1 maintient maintenant un verrou sur la ligne afin que toute autre session tentant de modifier la ligne attende de voir si S1 est validée ou annulée. Toute lecture de la table peut toujours voir l'ancienne ligne, à moins qu'ils ne tentent de la verrouiller avec SELECT FOR UPDATE
ou SELECT FOR SHARE
.
S2-1 s'exécute (mais est bloqué car S1 a un verrou en écriture)
S2 doit maintenant attendre le résultat de S1. Si S1 devait revenir en arrière plutôt que valider, S2 supprimerait la ligne. Notez que si S1 insérait une nouvelle version avant de revenir en arrière, la nouvelle version n’aurait jamais été là du point de vue d’une autre transaction, et l’ancienne version n’aurait pas été supprimée du point de vue d’une autre transaction.
S1-2 pistes (1 ligne insérée)
Cette rangée est indépendante de l'ancienne. S'il y avait eu une mise à jour de la ligne avec id = 1, l'ancienne version et la nouvelle version seraient liées et S2 pourrait supprimer la version mise à jour de la ligne lorsqu'elle serait débloquée. Le fait qu'une nouvelle ligne ait les mêmes valeurs qu'une ligne existante dans le passé ne la rend pas identique à une version mise à jour de cette ligne.
S1-3 s'exécute, libérant le verrou en écriture
Donc, les modifications de S1 sont persistées. Une rangée est partie. Une ligne a été ajoutée.
S2-1 s'exécute, maintenant qu'il peut obtenir le verrou. Mais rapporte 0 lignes supprimées. HUH ???
Ce qui se passe en interne, c'est qu'il y a un pointeur d'une version d'une ligne à la version suivante de cette même ligne si elle est mise à jour. Si la ligne est supprimée, il n'y a pas de version suivante. Lorsqu'une READ COMMITTED
transaction sort d'un blocage sur un conflit d'écriture, elle suit cette chaîne de mise à jour jusqu'à la fin. si la ligne n'a pas été supprimée et si elle répond toujours aux critères de sélection de la requête, elle sera traitée. Cette ligne a été supprimée, la requête de S2 continue.
S2 peut ou non accéder à la nouvelle ligne lors de son balayage de la table. Si tel est le cas, il verra que la nouvelle ligne a été créée après le lancement de l' DELETE
instruction S2 et ne fait donc pas partie de l'ensemble des lignes visibles.
Si PostgreSQL devait redémarrer l'intégralité de l'instruction DELETE de S2 depuis le début avec un nouvel instantané, celle-ci se comporterait de la même manière que SQL Server. La communauté PostgreSQL n'a pas choisi de le faire pour des raisons de performances. Dans ce cas simple, vous ne remarqueriez jamais la différence de performances, mais si vous aviez dix millions de lignes dans une situation de DELETE
blocage, vous le feriez certainement. Dans ce cas, PostgreSQL a choisi la performance, car la version la plus rapide est toujours conforme aux exigences de la norme.
S2-2 s'exécute, signale une violation de contrainte de clé unique
Bien sûr, la ligne existe déjà. C'est la partie la moins surprenante de la photo.
Bien qu'il y ait un comportement surprenant ici, tout est conforme au standard SQL et à la limite de ce qui est "spécifique à l'implémentation" selon le standard. Cela peut certainement être surprenant si vous supposez que le comportement d'une autre implémentation sera présent dans toutes les implémentations, mais PostgreSQL essaye très difficilement d'éviter les échecs de sérialisation dans le READ COMMITTED
niveau d'isolement et autorise certains comportements qui diffèrent des autres produits pour y parvenir.
Personnellement, je ne suis pas un grand fan du READ COMMITTED
niveau d’isolation des transactions dans l’implémentation d’ un produit. Ils permettent tous à des conditions de concurrence de créer des comportements surprenants d’un point de vue transactionnel. Une fois que quelqu'un s'habitue aux comportements étranges permis par un produit, ils ont tendance à considérer cela comme "normal" et les compromis choisis par un autre produit bizarre. Mais chaque produit doit faire un compromis quelconque pour tout mode non implémenté SERIALIZABLE
. Les développeurs de PostgreSQL ont choisi de tracer la ligne READ COMMITTED
pour minimiser le blocage (les lectures ne bloquent pas les écritures et les écritures ne bloquent pas les lectures) et minimisent les risques d'échec de la sérialisation.
La norme exige que les SERIALIZABLE
transactions soient la transaction par défaut, mais la plupart des produits ne le font pas, car cela nuit aux performances des niveaux d'isolation des transactions plus laxistes. Certains produits n'offrent même pas de transactions véritablement sérialisables lors de la SERIALIZABLE
sélection, notamment Oracle et les versions de PostgreSQL antérieures à la 9.1. Cependant, utiliser véritablement des SERIALIZABLE
transactions est le seul moyen d’éviter des effets surprenants liés aux conditions de concurrence, et les SERIALIZABLE
transactions doivent toujours soit bloquer pour éviter les conditions de concurrence, soit annuler certaines transactions pour éviter une situation de concurrence en développement. L'implémentation la plus courante des SERIALIZABLE
transactions est le verrouillage strict en deux phases (S2PL), qui présente des défaillances de blocage et de sérialisation (sous la forme d'interblocages).
Divulgation complète: j'ai travaillé avec Dan Ports du MIT pour ajouter des transactions véritablement sérialisables à PostgreSQL version 9.1 en utilisant une nouvelle technique appelée Serializable Snapshot Isolation.