Quando se trata de atualizar uma linha, muitas ferramentas ORM emitem uma instrução UPDATE que define todas as colunas associadas a essa entidade específica .
A vantagem é que você pode facilmente agrupar as instruções de atualização, pois a UPDATE
instrução é a mesma, independentemente do atributo de entidade que você altera. Além disso, você também pode usar o cache de instruções do servidor e do lado do cliente.
Portanto, se eu carregar uma entidade e definir apenas uma única propriedade:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Todas as colunas serão alteradas:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Agora, supondo que também tenhamos um índice na title
propriedade, o banco de dados não deve perceber que o valor não mudou de qualquer maneira?
No presente artigo , Markus Winand diz:
A atualização em todas as colunas mostra o mesmo padrão que já observamos nas seções anteriores: o tempo de resposta aumenta com cada índice adicional.
Gostaria de saber por que essa sobrecarga, pois o banco de dados carrega a página de dados associada do disco na memória e, assim, pode descobrir se um valor da coluna precisa ser alterado ou não.
Mesmo para índices, não é necessário reequilibrar nada, pois os valores do índice não são alterados para as colunas que não foram alteradas, mas elas foram incluídas no UPDATE.
Será que os índices B + Tree associados às colunas redundantes inalteradas também precisam ser navegados, apenas para o banco de dados perceber que o valor da folha ainda é o mesmo?
Obviamente, algumas ferramentas ORM permitem que você atualize apenas as propriedades alteradas:
UPDATE post
SET score = 12,
WHERE id = 1
Mas esse tipo de UPDATE nem sempre pode se beneficiar de atualizações em lote ou cache de instruções quando propriedades diferentes são alteradas para linhas diferentes.
fonte
UPDATE
é praticamente equivalente a umDELETE
+INSERT
(porque você realmente criar uma nova V ersão da linha). A sobrecarga é alta e aumenta com o número de índices , especialmente se muitas das colunas que as compõem forem realmente atualizadas e a árvore (ou qualquer outra coisa) usada para representar o índice precisar de uma alteração significativa. Não é o número de colunas atualizadas que é relevante, mas se você atualiza uma parte da coluna de um índice.Respostas:
Sei que você se preocupa
UPDATE
principalmente com o desempenho, mas como colega de manutenção de "ORM", deixe-me dar outra perspectiva sobre o problema de distinguir entre valores "alterados" , "nulos" e "padrão" , que são três coisas diferentes no SQL, mas possivelmente apenas uma coisa no Java e na maioria dos ORMs:Traduzindo sua justificativa para
INSERT
declaraçõesSeus argumentos a favor da capacidade de armazenamento e cache de instruções são verdadeiros da mesma maneira para
INSERT
instruções e paraUPDATE
instruções. Mas, no caso deINSERT
declarações, a omissão de uma coluna da declaração tem uma semântica diferente da que emUPDATE
. Significa aplicarDEFAULT
. Os dois seguintes são semanticamente equivalentes:Isso não é verdade para
UPDATE
, onde os dois primeiros são semanticamente equivalentes e o terceiro tem um significado totalmente diferente:A maioria das APIs de clientes de banco de dados, incluindo JDBC e, consequentemente, JPA, não permite vincular uma
DEFAULT
expressão a uma variável de ligação - principalmente porque os servidores também não permitem isso. Se você deseja reutilizar a mesma instrução SQL pelos motivos mencionados de capacidade de manipulação e armazenamento em cache, use a seguinte instrução nos dois casos (supondo que(a, b, c)
todas as colunas estejamt
):E como
c
não está definido, você provavelmente ligaria o Javanull
à terceira variável de ligação, porque muitos ORMs também não podem distinguir entreNULL
eDEFAULT
( jOOQ , por exemplo, sendo uma exceção aqui). Eles apenas veem Javanull
e não sabem se isso significaNULL
(como no valor desconhecido) ouDEFAULT
(como no valor não inicializado).Em muitos casos, essa distinção não importa, mas, se sua coluna c estiver usando algum dos seguintes recursos, a instrução estará simplesmente errada :
DEFAULT
cláusulaVoltar para
UPDATE
declaraçõesEmbora o acima seja verdadeiro para todos os bancos de dados, posso garantir que o problema do acionador também é verdadeiro para o banco de dados Oracle. Considere o seguinte SQL:
Ao executar o procedimento acima, você verá a seguinte saída:
Como você pode ver, a instrução que sempre atualiza todas as colunas sempre aciona o gatilho para todas as colunas, enquanto as instruções que atualizam apenas as colunas que foram alteradas acionam apenas os gatilhos que estão ouvindo essas alterações específicas.
Em outras palavras:
O comportamento atual do Hibernate que você está descrevendo é incompleto e pode até ser considerado errado na presença de gatilhos (e provavelmente outras ferramentas).
Pessoalmente, acho que seu argumento de otimização de cache de consulta é superestimado no caso de SQL dinâmico. Claro, haverá mais algumas consultas nesse cache e um pouco mais de trabalho de análise a ser feito, mas isso geralmente não é um problema para
UPDATE
instruções dinâmicas , muito menos do que paraSELECT
.O lote é certamente um problema, mas, na minha opinião, uma única atualização não deve ser normalizada para atualizar todas as colunas apenas porque existe uma pequena possibilidade de a declaração ser recuperável. Provavelmente, o ORM pode coletar sub-lotes de instruções idênticas consecutivas e colocá-las em lote em vez do "lote inteiro" (caso o ORM seja capaz de rastrear a diferença entre "alterado" , "nulo" e "padrão"
fonte
DEFAULT
caso de uso pode ser tratado por@DynamicInsert
. A situação do TRIGGER também pode ser resolvida usando cheques comoWHEN (NEW.b <> OLD.b)
ou apenas mudar para@DynamicUpdate
.Eu acho que a resposta é - é complicado . Tentei escrever uma prova rápida usando uma
longtext
coluna no MySQL, mas a resposta é um pouco inconclusiva. Prova primeiro:Portanto, há uma pequena diferença de tempo entre lento + valor alterado e lento + sem valor alterado. Então decidi olhar para outra métrica, que era páginas escritas:
Portanto, parece que o tempo aumentou, pois é necessário comparar para confirmar que o valor em si não foi modificado, o que, no caso de um texto longo de 1G, leva tempo (porque está dividido em várias páginas). Mas a modificação em si não parece agitar o log de refazer.
Eu suspeito que, se os valores são colunas regulares que estão na página, a comparação adiciona apenas um pouco de sobrecarga. E supondo que a mesma otimização se aplique, estas não são operacionais quando se trata da atualização.
Resposta mais longa
Na verdade, acho que o ORM não deve eliminar colunas que foram modificadas ( mas não alteradas ), pois essa otimização tem efeitos colaterais estranhos.
Considere o seguinte no pseudo-código:
O resultado se o ORM "otimizar" a modificação sem alteração:
O resultado se o ORM enviou todas as modificações ao servidor:
O caso de teste aqui depende do
repeatable-read
isolamento (padrão do MySQL), mas também existe uma janela de tempo para oread-committed
isolamento em que a leitura da sessão2 ocorre antes do commit da sessão1.Dito de outra forma: a otimização só é segura se você emitir a
SELECT .. FOR UPDATE
para ler as linhas seguidas de umaUPDATE
.SELECT .. FOR UPDATE
não usa MVCC e sempre lê a versão mais recente das linhas.Edit: Verifique se o conjunto de dados do caso de teste estava 100% na memória. Resultados de tempo ajustados.
fonte