Mat y Erwin tienen razón, y solo estoy agregando otra respuesta para ampliar aún más lo que dijeron de una manera que no cabe en un comentario. Dado que sus respuestas no parecen satisfacer a todos, y hubo una sugerencia de que se debería consultar a los desarrolladores de PostgreSQL, y yo soy uno, elaboraré.
El punto importante aquí es que, según el estándar SQL, dentro de una transacción que se ejecuta en el READ COMMITTED
nivel de aislamiento de la transacción, la restricción es que el trabajo de las transacciones no confirmadas no debe ser visible. Cuando el trabajo de las transacciones confirmadas se hace visible, depende de la implementación. Lo que está señalando es una diferencia en cómo dos productos han elegido implementar eso. Ninguna implementación viola los requisitos de la norma.
Esto es lo que sucede dentro de PostgreSQL, en detalle:
S1-1 se ejecuta (1 fila eliminada)
La fila anterior se deja en su lugar, porque S1 aún puede retroceder, pero S1 ahora mantiene un bloqueo en la fila para que cualquier otra sesión que intente modificar la fila espere para ver si S1 se compromete o retrocede. Cualquier lectura de la tabla aún puede ver la fila anterior, a menos que intenten bloquearla con SELECT FOR UPDATE
o SELECT FOR SHARE
.
S2-1 se ejecuta (pero está bloqueado ya que S1 tiene un bloqueo de escritura)
S2 ahora tiene que esperar para ver el resultado de S1. Si S1 retrocediera en lugar de confirmar, S2 eliminaría la fila. Tenga en cuenta que si S1 inserta una nueva versión antes de deshacer, la nueva versión nunca habría estado allí desde la perspectiva de ninguna otra transacción, ni la versión anterior se habría eliminado desde la perspectiva de cualquier otra transacción.
S1-2 carreras (1 fila insertada)
Esta fila es independiente de la anterior. Si hubiera habido una actualización de la fila con id = 1, las versiones antigua y nueva estarían relacionadas, y S2 podría eliminar la versión actualizada de la fila cuando se desbloqueara. Que una nueva fila tenga los mismos valores que alguna fila que existía en el pasado no es lo mismo que una versión actualizada de esa fila.
S1-3 se ejecuta, liberando el bloqueo de escritura
Entonces los cambios de S1 son persistentes. Una fila se ha ido. Se ha agregado una fila.
S2-1 se ejecuta, ahora que puede obtener el bloqueo. Pero informa 0 filas eliminadas. HUH ???
Lo que sucede internamente es que hay un puntero de una versión de una fila a la siguiente versión de esa misma fila si se actualiza. Si se elimina la fila, no hay una versión siguiente. Cuando una READ COMMITTED
transacción se despierta de un bloque en un conflicto de escritura, sigue esa cadena de actualización hasta el final; si la fila no se ha eliminado y si aún cumple con los criterios de selección de la consulta, se procesará. Esta fila se ha eliminado, por lo que la consulta de S2 continúa.
S2 puede o no llegar a la nueva fila durante su exploración de la tabla. Si lo hace, verá que la nueva fila se creó después de DELETE
que se inició la instrucción de S2 , por lo que no es parte del conjunto de filas visibles para ella.
Si PostgreSQL reiniciara la declaración DELETE completa de S2 desde el principio con una nueva instantánea, se comportaría igual que SQL Server. La comunidad PostgreSQL no ha elegido hacer eso por razones de rendimiento. En este caso simple, nunca notaría la diferencia en el rendimiento, pero si tuviera diez millones de filas en un DELETE
momento en que se bloqueó, ciertamente lo haría. Aquí hay una compensación donde PostgreSQL ha elegido el rendimiento, ya que la versión más rápida aún cumple con los requisitos del estándar.
S2-2 se ejecuta, informa una violación de restricción de clave única
Por supuesto, la fila ya existe. Esta es la parte menos sorprendente de la imagen.
Si bien aquí hay un comportamiento sorprendente, todo está en conformidad con el estándar SQL y dentro de los límites de lo que es "específico de implementación" de acuerdo con el estándar. Ciertamente puede ser sorprendente si está asumiendo que el comportamiento de alguna otra implementación estará presente en todas las implementaciones, pero PostgreSQL se esfuerza mucho por evitar fallas de serialización en el READ COMMITTED
nivel de aislamiento, y permite algunos comportamientos que difieren de otros productos para lograrlo.
Ahora, personalmente, no soy un gran admirador del READ COMMITTED
nivel de aislamiento de transacciones en la implementación de ningún producto. Todos permiten que las condiciones de carrera creen comportamientos sorprendentes desde un punto de vista transaccional. Una vez que alguien se acostumbra a los comportamientos extraños permitidos por un producto, tiende a considerar que eso es "normal" y que las compensaciones elegidas por otro producto son extrañas. Pero cada producto tiene que hacer algún tipo de compensación por cualquier modo que no se implemente realmente SERIALIZABLE
. Donde los desarrolladores de PostgreSQL han elegido trazar la línea READ COMMITTED
es minimizar el bloqueo (las lecturas no bloquean las escrituras y las escrituras no bloquean las lecturas) y minimizar la posibilidad de fallas de serialización.
El estándar requiere que las SERIALIZABLE
transacciones sean las predeterminadas, pero la mayoría de los productos no lo hacen porque causa un impacto en el rendimiento en los niveles de aislamiento de transacciones más laxos. Algunos productos ni siquiera proporcionan transacciones verdaderamente serializables cuando SERIALIZABLE
se elige, especialmente Oracle y versiones de PostgreSQL anteriores a 9.1. Pero el uso de SERIALIZABLE
transacciones reales es la única forma de evitar efectos sorprendentes de las condiciones de carrera, y las SERIALIZABLE
transacciones siempre deben bloquearse para evitar las condiciones de carrera o revertir algunas transacciones para evitar una condición de carrera en desarrollo. La implementación más común de las SERIALIZABLE
transacciones es el bloqueo estricto de dos fases (S2PL), que tiene fallas tanto de bloqueo como de serialización (en forma de puntos muertos).
Revelación completa: trabajé con Dan Ports de MIT para agregar transacciones verdaderamente serializables a PostgreSQL versión 9.1 utilizando una nueva técnica llamada Aislamiento de instantáneas serializables.