PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Vários threads podem causar atualizações duplicadas no conjunto restrito?


Suas garantias declaradas se aplicam neste caso simples, mas não necessariamente em consultas um pouco mais complexas. Veja o final da resposta para exemplos.

O caso simples


Supondo que col1 seja único, tenha exatamente um valor "2" ou tenha ordenação estável, então cada UPDATE corresponde às mesmas linhas na mesma ordem:

O que acontecerá para esta consulta é que os threads encontrarão a linha com col=2 e todos tentarão obter um bloqueio de gravação nessa tupla. Exatamente um deles terá sucesso. As outras irão bloquear esperando a transação da primeira thread ser confirmada.

Esse primeiro tx escreverá, confirmará e retornará uma contagem de linhas de 1. O commit liberará o bloqueio.

Os outros txs tentarão novamente agarrar a fechadura. Um por um eles terão sucesso. Cada transação, por sua vez, passará pelo seguinte processo:
  • Obtenha o bloqueio de gravação na tupla contestada.
  • Verifique novamente o WHERE col=2 condição depois de obter o bloqueio.
  • A nova verificação mostrará que a condição não corresponde mais, portanto, UPDATE pulará essa linha.
  • A UPDATE não tem outras linhas, então ele reportará zero linhas atualizadas.
  • Confirme, liberando o bloqueio para o próximo tx tentando pegá-lo.

Nesse caso simples, o bloqueio em nível de linha e a nova verificação de condição serializam efetivamente as atualizações. Em casos mais complexos, nem tanto.

Você pode facilmente demonstrar isso. Abra digamos quatro sessões psql. Na primeira, bloqueie a tabela com BEGIN; LOCK TABLE test; . No restante das sessões, execute UPDATE idêntico s - eles bloquearão no bloqueio do nível da mesa. Agora solte o bloqueio por COMMIT em sua primeira sessão. Veja-os correr. Apenas um relatará uma contagem de linhas de 1, os outros relatarão 0. Isso é facilmente automatizado e com script para repetição e escala para mais conexões/threads.

Para saber mais, leia as regras para escrita simultânea , página 11 de problemas de simultaneidade do PostgreSQL - e depois leia o resto dessa apresentação.

E se col1 não for exclusivo?


Como Kevin observou nos comentários, se col não é exclusivo, então você pode corresponder a várias linhas, então diferentes execuções do UPDATE poderia obter pedidos diferentes. Isso pode acontecer se eles escolherem planos diferentes (digamos que um seja através de um PREPARE e EXECUTE e outro é direto, ou você está mexendo com o enable_ GUCs) ou se o plano que todos usam usa um tipo instável de valores iguais. Se eles obtiverem as linhas em uma ordem diferente, tx1 bloqueará uma tupla, tx2 bloqueará outra, então cada um tentará obter bloqueios nas tuplas já bloqueadas um do outro. O PostgreSQL abortará um deles com uma exceção de deadlock. Esta é mais uma boa razão pela qual todos seu código de banco de dados deve sempre esteja preparado para repetir as transações.

Se você tiver o cuidado de garantir que UPDATE simultâneo s sempre obter as mesmas linhas na mesma ordem, você ainda pode confiar no comportamento descrito na primeira parte da resposta.

Frustrantemente, o PostgreSQL não oferece UPDATE ... ORDER BY portanto, garantir que suas atualizações sempre selecionem as mesmas linhas na mesma ordem não é tão simples quanto você gostaria. A SELECT ... FOR UPDATE ... ORDER BY seguido por um UPDATE separado muitas vezes é mais seguro.

Consultas mais complexas, sistemas de filas


Se você estiver fazendo consultas com várias fases, envolvendo várias tuplas ou condições diferentes de igualdade, poderá obter resultados surpreendentes que diferem dos resultados de uma execução serial. Em particular, execuções simultâneas de qualquer coisa como:
UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

ou outros esforços para construir um sistema de "fila" simples vai *falha* em funcionar como você espera. Consulte os documentos PostgreSQL sobre simultaneidade e esta apresentação para mais informações.

Se você deseja uma fila de trabalho apoiada por um banco de dados, existem soluções bem testadas que lidam com todos os casos de canto surpreendentemente complicados. Um dos mais populares é o PgQ . Há um útil papel PgCon sobre o assunto e uma pesquisa no Google por 'postgresql queue' está cheio de resultados úteis.

BTW, em vez de uma LOCK TABLE você pode usar SELECT 1 FROM test WHERE col = 2 FOR UPDATE; para obter um bloqueio de gravação apenas na tupla. Isso bloqueará atualizações nele, mas não bloqueará gravações em outras tuplas ou bloqueará qualquer leitura. Isso permite simular diferentes tipos de problemas de simultaneidade.