As peculiaridades dos mecanismos internos do PostgreSQL permitem que ele seja muito rápido em algumas situações e “pouco rápido” em outras. Hoje vamos nos concentrar em um exemplo clássico de conflito entre como um SGBD funciona e o que o desenvolvedor faz com ele - Princípios UPDATE vs MVCC.
Breve história de
Quando uma linha é modificada por um comando UPDATE, duas operações são realmente executadas: DELETE e INSERT. EM versão atual da string xmax é igual ao número da transação que executou o UPDATE. Então é criado nova versão a mesma linha; seu valor xmin coincide com o valor xmax da versão anterior.
Algum tempo após a conclusão desta transação, a versão antiga ou nova, dependendo COMMIT/ROOLBACK
, será reconhecido "morto" (tuplas mortas) ao passar VACUUM
de acordo com a tabela e apagado.
Mas isso não acontecerá imediatamente, mas problemas com os “mortos” podem ser adquiridos muito rapidamente - com repetidos ou
Nº 1: gosto de movê-lo
Digamos que seu método de lógica de negócio esteja funcionando sozinho e de repente ele percebe que seria necessário atualizar o campo X em algum registro:
UPDATE tbl SET X = <newX> WHERE pk = $1;
Então, à medida que a execução avança, verifica-se que o campo Y também deve ser atualizado:
UPDATE tbl SET Y = <newY> WHERE pk = $1;
... e depois também Z - por que perder tempo com ninharias?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;
Quantas versões deste registro temos agora no banco de dados? Sim, 4 peças! Destes, um é relevante e 3 terão que ser limpos depois de você pelo [auto]VACUUM.
Não faça assim! Usar atualizando todos os campos em uma solicitação — quase sempre a lógica do método pode ser alterada assim:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;
#2: O uso É DISTINTO DE, Luke!
Então, você ainda queria
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;
Uma solicitação semelhante a esta é encontrada com bastante frequência e quase sempre não para preencher um novo campo vazio, mas para corrigir alguns erros nos dados. Ao mesmo tempo, ela mesma a exatidão dos dados existentes não é levada em consideração - mas em vão! Ou seja, o disco é reescrito, mesmo que contivesse exatamente o que se queria – mas por quê? Vamos consertar:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;
Muitas pessoas não estão cientes da existência de um operador tão maravilhoso, então aqui está uma folha de dicas sobre IS DISTINCT FROM
e outros operadores lógicos para ajudar:
... e um pouco sobre operações em complexos ROW()
-expressões:
#3: Reconheço meu amor por... bloquear
Corre dois processos paralelos idênticos, cada um dos quais tenta marcar a entrada como “em andamento”:
UPDATE tbl SET processing = TRUE WHERE pk = $1;
Mesmo que esses processos realmente façam coisas independentes um do outro, mas dentro do mesmo ID, o segundo cliente ficará “bloqueado” nesta solicitação até que a primeira transação seja concluída.
Decisão nº 1: a tarefa é reduzida à anterior
Vamos adicioná-lo novamente IS DISTINCT FROM
:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;
Dessa forma, a segunda solicitação simplesmente não mudará nada no banco de dados, tudo já está como deveria estar - portanto, o bloqueio não ocorrerá. A seguir, processamos o fato de “não encontrar” o registro no algoritmo aplicado.
Decisão nº 2: bloqueios consultivos
Um grande tópico para um artigo separado, no qual você pode ler sobre
Decisão nº 3: chamadas estúpidas
Mas é exatamente isso que deveria acontecer com você trabalho simultâneo com o mesmo registro? Ou você bagunçou os algoritmos de chamada da lógica de negócios do lado do cliente, por exemplo? E se você pensar sobre isso?..
Fonte: habr.com