sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Pueden múltiples subprocesos causar actualizaciones duplicadas en un conjunto restringido?

Sus garantías declaradas se aplican en este caso simple, pero no necesariamente en consultas un poco más complejas. Vea el final de la respuesta para ver ejemplos.

El caso sencillo

Suponiendo que col1 es único, tiene exactamente un valor "2" o tiene un orden estable, por lo que cada UPDATE coincide con las mismas filas en el mismo orden:

Lo que sucederá con esta consulta es que los subprocesos encontrarán la fila con col=2 y todos intentarán obtener un bloqueo de escritura en esa tupla. Exactamente uno de ellos tendrá éxito. Los demás bloquearán la espera de que se confirme la transacción del primer subproceso.

Ese primer tx escribirá, confirmará y devolverá un recuento de filas de 1. La confirmación liberará el bloqueo.

Los otros TX volverán a intentar agarrar el candado. Uno a uno lo conseguirán. Cada transacción pasará a su vez por el siguiente proceso:

  • Obtenga el bloqueo de escritura en la tupla impugnada.
  • Vuelva a comprobar WHERE col=2 condición después de obtener el candado.
  • La nueva verificación mostrará que la condición ya no coincide, por lo que UPDATE se saltará esa fila.
  • La UPDATE no tiene otras filas, por lo que informará cero filas actualizadas.
  • Commit, liberando el bloqueo para el próximo tx que intente controlarlo.

En este caso simple, el bloqueo a nivel de fila y la verificación de condición serializan efectivamente las actualizaciones. En casos más complejos, no tanto.

Puedes demostrar esto fácilmente. Abra digamos cuatro sesiones de psql. En el primero, bloquea la tabla con BEGIN; LOCK TABLE test; . En el resto de las sesiones ejecuta idénticas UPDATE s - se bloquearán en el bloqueo del nivel de la mesa. Ahora suelte el bloqueo mediante COMMIT ting su primera sesión. Míralos correr. Solo uno informará un recuento de filas de 1, los otros informarán 0. Esto se automatiza fácilmente y se programa para repetir y escalar a más conexiones/subprocesos.

Para obtener más información, lea las reglas para escritura simultánea , página 11 de Problemas de concurrencia de PostgreSQL - y luego lea el resto de esa presentación.

¿Y si col1 no es único?

Como señaló Kevin en los comentarios, si col no es único, por lo que puede hacer coincidir varias filas, luego diferentes ejecuciones de UPDATE podría obtener diferentes pedidos. Esto puede suceder si eligen diferentes planes (digamos que uno es a través de PREPARE y EXECUTE y otro es directo, o te estás metiendo con el enable_ GUC) o si el plan que todos usan usa un tipo inestable de valores iguales. Si obtienen las filas en un orden diferente, entonces tx1 bloqueará una tupla, tx2 bloqueará otra, luego cada uno intentará obtener bloqueos en las tuplas ya bloqueadas de los demás. PostgreSQL abortará uno de ellos con una excepción de interbloqueo. Esta es otra buena razón por la que todas el código de su base de datos debe siempre prepárate para volver a intentar transacciones.

Si tienes cuidado de asegurarte de UPDATE concurrentes Si siempre obtiene las mismas filas en el mismo orden, aún puede confiar en el comportamiento descrito en la primera parte de la respuesta.

Frustrantemente, PostgreSQL no ofrece UPDATE ... ORDER BY por lo tanto, asegurarse de que sus actualizaciones siempre seleccionen las mismas filas en el mismo orden no es tan simple como podría desear. A SELECT ... FOR UPDATE ... ORDER BY seguido de un UPDATE separado suele ser lo más seguro.

Consultas más complejas, sistemas de colas

Si está realizando consultas con múltiples fases, que involucran múltiples tuplas o condiciones distintas a la igualdad, puede obtener resultados sorprendentes que difieren de los resultados de una ejecución en serie. En particular, ejecuciones simultáneas de algo como:

UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

u otros esfuerzos para construir un sistema de "cola" simple will *no* funciona como esperas. Consulte los Documentos de PostgreSQL sobre simultaneidad y esta presentación para más información.

Si desea una cola de trabajo respaldada por una base de datos, existen soluciones bien probadas que manejan todos los casos de esquina sorprendentemente complicados. Uno de los más populares es PgQ . Hay un útil documento de PgCon sobre el tema, y ​​una búsqueda en Google de 'postgresql queue' está lleno de resultados útiles.

Por cierto, en lugar de una LOCK TABLE puede usar SELECT 1 FROM test WHERE col = 2 FOR UPDATE; para obtener un bloqueo de escritura en solo eso en tupla. Eso bloqueará las actualizaciones en su contra, pero no bloqueará las escrituras en otras tuplas ni bloqueará las lecturas. Eso le permite simular diferentes tipos de problemas de concurrencia.