He oído hablar de problemas de concurrencia como ese en MySQL antes. No es así en Postgres.
Los bloqueos de nivel de fila incorporados en el READ COMMITTEDnivel de aislamiento de transacción predeterminado son suficientes.
Sugiero una sola declaración con un CTE modificador de datos (algo que MySQL tampoco tiene) porque es conveniente pasar valores de una tabla a otra directamente (si es necesario). Si no necesita nada de la coupontabla, puede usar una transacción con declaraciones separadas UPDATEy INSERTtambién.
WITH upd AS (
UPDATE coupon
SET used = true
WHERE coupon_id = 123
AND NOT used
RETURNING coupon_id, other_column
)
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;
Debería ser raro que más de una transacción intente canjear el mismo cupón. Tienen un número único, ¿no? Sin embargo, más de una transacción que intente en el mismo momento en el tiempo debería ser mucho más rara. (¿Tal vez un error de aplicación o alguien tratando de jugar el sistema?)
Sea como fuere, el UPDATEúnico tiene éxito exactamente para una transacción, pase lo que pase. Un UPDATEadquiere un bloqueo de nivel de fila en cada fila de destino antes de actualizar. Si una transacción concurrente intenta UPDATEla misma fila, verá el bloqueo en la fila y esperará hasta que finalice la transacción de bloqueo ( ROLLBACKo COMMIT), luego será el primero en la cola de bloqueo:
Si se confirma, vuelva a verificar la condición. Si todavía está NOT used, bloquee la fila y continúe. De lo contrario, UPDATEahora no encuentra ninguna fila de calificación y no hace nada , no devuelve ninguna fila, por lo que INSERTtampoco hace nada.
Si retrocede, bloquee la fila y continúe.
No hay potencial para una condición de carrera .
No hay potencial para un punto muerto a menos que coloque más escrituras en la misma transacción o bloquee más filas que solo la única.
El INSERTes sin preocupaciones. Si, por algún error, el coupon_idya está en la logtabla (y tiene una restricción UNIQUE o PK activada log.coupon_id), la transacción completa se revertirá después de una violación única. Indicaría un estado ilegal en su base de datos. Si la declaración anterior es la única forma de escribir en la logtabla, eso nunca debería ocurrir.