Suas opções são:
-
Executar emSERIALIZABLEisolamento. 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 umUNIQUErestrição e tente novamente em caso de falha, como você observou. Mesmos problemas acima.
-
Se houver um objeto pai, você podeSELECT ... FOR UPDATEo objeto pai antes de fazer seumaxconsulta. Nesse caso, vocêSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE. Você está usandobarcomo um bloqueio para todos osfoos 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 .