Es muy difícil hacer cumplir las reglas de negocio relativas a la integridad de los datos utilizando transacciones Read Committed porque la vista de los datos va cambiando con cada sentencia, e incluso una sola sentencia puede no limitarse a la instantánea de la sentencia si se produce un conflicto de escritura.
Aunque una transacción Repeatable Read tiene una vista estable de los datos a lo largo de su ejecución, existe un problema sutil con el uso de instantáneas MVCC para las comprobaciones de consistencia de datos, que implica algo conocido como conflictos de lectura/escritura. Si una transacción escribe datos y una transacción concurrente intenta leer los mismos datos (ya sea antes o después de la escritura), no puede ver el trabajo de la otra transacción. El lector parece entonces haberse ejecutado primero, independientemente de cuál haya comenzado primero o cuál se haya comprometido primero. Si eso es todo, no hay ningún problema, pero si el lector también escribe datos que son leídos por una transacción concurrente, existe ahora una transacción que parece haberse ejecutado antes que cualquiera de las transacciones mencionadas anteriormente. Si la transacción que parece haberse ejecutado en último lugar se compromete primero, es muy fácil que aparezca un ciclo en un gráfico del orden de ejecución de las transacciones. Cuando aparece un ciclo de este tipo, las comprobaciones de integridad no funcionarán correctamente sin ayuda.
Como se mencionó en la la Section 13.2.3, las transacciones Serializables son simplemente transacciones Repeatable Read que añaden un monitoreo no bloqueante para patrones peligrosos de conflictos de lectura/escritura. Cuando se detecta un patrón que podría causar un ciclo en el orden aparente de ejecución, una de las transacciones implicadas se revierte para romper el ciclo.
Si se utiliza el nivel de aislamiento de transacciones Serializable para todas las escrituras y para todas las lecturas que necesiten una vista consistente de los datos, no se requiere ningún otro esfuerzo para garantizar la consistencia. El software de otros entornos que esté escrito para utilizar transacciones serializables para garantizar la consistencia debería “simplemente funcionar” a este respecto en PostgreSQL.
Cuando se utiliza esta técnica, se evitará crear una carga innecesaria para los programadores de aplicaciones
si el software de la aplicación pasa por un framework que reintente automáticamente las transacciones que se
revierten con un fallo de serialización. Puede ser una buena idea establecer
default_transaction_isolation a serializable.
También sería prudente tomar medidas para asegurar que no se utilice ningún otro nivel de aislamiento de
transacciones, ya sea de forma inadvertida o para eludir las comprobaciones de integridad, mediante la
comprobación del nivel de aislamiento de las transacciones en los disparadores (triggers).
Consulta la la Section 13.2.3 para ver sugerencias sobre el rendimiento.
Este nivel de protección de la integridad mediante transacciones Serializables aún no se extiende al modo de espera activa (hot standby) (Section 26.4) ni a las réplicas lógicas. Due to that, quienes utilicen espera activa o replicación lógica pueden querer utilizar Repeatable Read y bloqueos explícitos en el primario.
Cuando son posibles escrituras no serializables, para garantizar la validez actual de una fila y protegerla
contra actualizaciones concurrentes se debe utilizar SELECT FOR UPDATE,
SELECT FOR SHARE o una sentencia LOCK TABLE adecuada.
(SELECT FOR UPDATE y SELECT FOR SHARE bloquean solo las filas devueltas
contra actualizaciones concurrentes, mientras que LOCK TABLE bloquea la tabla completa).
Esto debe tenerse en cuenta al portar aplicaciones a PostgreSQL desde otros entornos.
También es de interés para quienes realizan la conversión desde otros entornos el hecho de que
SELECT FOR UPDATE no asegura que una transacción concurrente no actualice o elimine una
fila seleccionada.
Para hacer eso en PostgreSQL realmente debes actualizar la fila, incluso si no es
necesario cambiar ningún valor.
SELECT FOR UPDATE bloquea temporalmente a otras transacciones de
adquirir el mismo bloqueo o de ejecutar un UPDATE o DELETE que afectaría
a la fila bloqueada, pero una vez que la transacción que mantiene este bloqueo se compromete o se revierte,
una transacción bloqueada procederá con la operación en conflicto a menos que se haya realizado un
UPDATE real de la fila mientras se mantenía el bloqueo.
Las comprobaciones de validez global requieren una reflexión adicional bajo MVCC no serializable.
Por ejemplo, una aplicación bancaria puede desear comprobar que la suma de todos los créditos en una tabla
es igual a la suma de los débitos en otra tabla, cuando ambas tablas se están actualizando activamente.
Comparar los resultados de dos comandos SELECT sum(...) sucesivos no funcionará de manera
fiable en el modo Read Committed, ya que es probable que la segunda consulta incluya los resultados de
transacciones no contabilizadas por la primera. Realizar las dos sumas en una sola transacción de lectura
repetible proporcionará una imagen precisa de únicamente los efectos de las transacciones que se comprometieron
antes de que comenzara la transacción de lectura repetible — pero uno podría preguntarse legítimamente
si la respuesta sigue siendo relevante en el momento en que se entrega. Si la propia transacción de lectura
repetible aplicó algunos cambios antes de intentar realizar la comprobación de consistencia, la utilidad de
la comprobación se vuelve aún más discutible, ya que ahora incluye algunos pero no todos los cambios posteriores
al inicio de la transacción. En tales casos, una persona cuidadosa podría desear bloquear todas las tablas
necesarias para la comprobación, con el fin de obtener una imagen indiscutible de la realidad actual. Un
bloqueo en modo SHARE (o superior) garantiza que no hay cambios no comprometidos en la
tabla bloqueada, distintos de los de la transacción actual.
Ten en cuenta también que si se confía en el bloqueo explícito para evitar cambios concurrentes, se debe
utilizar el modo Read Committed, o bien en el modo Repeatable Read tener cuidado de obtener los bloqueos antes
de realizar las consultas. Un bloqueo obtenido por una transacción de lectura repetible garantiza que no hay
otras transacciones que modifiquen la tabla en ejecución, pero si la instantánea vista por la transacción es
anterior a la obtención del bloqueo, podría ser anterior a algunos cambios ya comprometidos en la tabla.
La instantánea de una transacción de lectura repetible se congela realmente al comienzo de su primera consulta
o comando de modificación de datos (SELECT, INSERT,
UPDATE, DELETE o MERGE), por lo que es posible
obtener bloqueos explícitamente antes de que la instantánea se congele.