Suas opções são:
-
Executar emSERIALIZABLE
isolamento. As transações interdependentes serão abortadas na confirmação como tendo uma falha de serialização. Você receberá muitos spams de log de erros e fará muitas tentativas, mas funcionará de maneira confiável.
-
Defina umUNIQUE
restrição e tente novamente em caso de falha, como você observou. Mesmos problemas acima.
-
Se houver um objeto pai, você podeSELECT ... FOR UPDATE
o objeto pai antes de fazer seumax
consulta. Nesse caso, vocêSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE
. Você está usandobar
como um bloqueio para todos osfoo
s com essebar_id
. Você pode então saber que é seguro continuar, desde que cada consulta que está fazendo seu incremento de contador faça isso de forma confiável. Isso pode funcionar muito bem.
Isso ainda faz uma consulta agregada para cada chamada, o que (por próxima opção) é desnecessário, mas pelo menos não envia spam ao log de erros como as opções acima.
-
Use uma mesa de balcão. Isto é o que eu faria. Ou embar
, ou em uma tabela lateral comobar_foo_counter
, adquira um ID de linha usando
UPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counter
ou a opção menos eficiente se sua estrutura não puder lidar comRETURNING
:
SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;
Então, na mesma transação , use a linha do contador gerada para onumber
. Quando você confirma, a linha da tabela de contador para essebar_id
é desbloqueado para a próxima consulta a ser usada. Se você reverter, a alteração será descartada.
Eu recomendo a abordagem do contador, usando uma mesa lateral dedicada para o contador em vez de adicionar uma coluna à
bar
. Isso é mais fácil de modelar e significa que você cria menos atualizações na bar
, o que pode diminuir a velocidade das consultas para bar
.