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 COMMITTED
isolamento 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 coupon
tabela, poderá usar uma transação com instruções UPDATE
e separadas INSERT
també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 UPDATE
adquire um bloqueio no nível da linha em cada linha de destino antes de atualizar. Se uma transação simultânea tentar UPDATE
a mesma linha, ela verá o bloqueio na linha e aguardará a conclusão da transação de bloqueio ( ROLLBACK
ou 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 UPDATE
agora não encontra nenhuma linha qualificativa e não faz nada , não retornando nenhuma linha, portanto a INSERT
també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_id
já estiver na log
tabela (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 log
tabela, isso nunca deve ocorrer.