Já ouvi falar de problemas de concorrência como esse no MySQL antes. Não é assim no Postgres.
Bloqueios internos no nível da linha no nível de READ COMMITTEDisolamento da transação padrão são suficientes.
Sugiro uma única declaração com um CTE modificador de dados (algo que o MySQL também não possui) porque é conveniente passar valores de uma tabela para outra diretamente (se você precisar disso). Se você não precisar de nada da coupontabela, poderá usar uma transação com instruções UPDATEe separadas INSERTtambém.
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;
É raro que mais de uma transação tente resgatar o mesmo cupom. Eles têm um número único, não têm? Mais de uma transação tentando no mesmo momento no tempo deve ser muito mais rara ainda. (Talvez um bug no aplicativo ou alguém tentando enganar o sistema?)
Seja como for, o UPDATEúnico consegue exatamente uma transação, não importa o quê. Um UPDATEadquire um bloqueio no nível da linha em cada linha de destino antes de atualizar. Se uma transação simultânea tentar UPDATEa mesma linha, ela verá o bloqueio na linha e aguardará a conclusão da transação de bloqueio ( ROLLBACKou COMMIT), sendo a primeira na fila de bloqueio:
Se confirmado, verifique novamente a condição. Se ainda estiver NOT used, trave a linha e continue. Caso contrário, o UPDATEagora não encontra nenhuma linha qualificativa e não faz nada , não retornando nenhuma linha, portanto a INSERTtambém não faz nada.
Se revertida, trave a linha e continue.
Não há potencial para uma condição de corrida .
Não há potencial para um conflito, a menos que você coloque mais gravações na mesma transação ou bloqueie mais linhas do que apenas uma.
O INSERTé livre de cuidados. Se, por algum erro, o coupon_idjá estiver na logtabela (e você tiver uma restrição UNIQUE ou PK ativada log.coupon_id), toda a transação será revertida após uma violação exclusiva. Indica um estado ilegal no seu banco de dados. Se a instrução acima for a única maneira de gravar na logtabela, isso nunca deve ocorrer.