Lorsqu'il s'agit de mettre à jour une ligne, de nombreux outils ORM émettent une instruction UPDATE qui définit chaque colonne associée à cette entité particulière .
L'avantage est que vous pouvez facilement regrouper les instructions de mise à jour car l' UPDATE
instruction est la même quel que soit l'attribut d'entité que vous modifiez. De plus, vous pouvez même utiliser la mise en cache des instructions côté serveur et côté client.
Donc, si je charge une entité et que je ne définis qu'une seule propriété:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Toutes les colonnes vont être modifiées:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Maintenant, en supposant que nous ayons également un index sur la title
propriété, la base de données ne devrait-elle pas se rendre compte que la valeur n'a pas changé de toute façon?
Dans cet article , Markus Winand dit:
La mise à jour sur toutes les colonnes montre le même schéma que nous avons déjà observé dans les sections précédentes: le temps de réponse augmente avec chaque index supplémentaire.
Je me demande pourquoi cette surcharge est due au fait que la base de données charge la page de données associée du disque dans la mémoire et qu'elle peut donc déterminer si une valeur de colonne doit être modifiée ou non.
Même pour les index, il ne rééquilibre rien car les valeurs d'index ne changent pas pour les colonnes qui n'ont pas changé, mais elles ont été incluses dans la MISE À JOUR.
Est-ce que les index B + Tree associés aux colonnes inchangées redondantes doivent également être parcourus, uniquement pour que la base de données se rende compte que la valeur de la feuille est toujours la même?
Bien sûr, certains outils ORM vous permettent de METTRE À JOUR uniquement les propriétés modifiées:
UPDATE post
SET score = 12,
WHERE id = 1
Mais ce type de MISE À JOUR peut ne pas toujours bénéficier des mises à jour par lots ou de la mise en cache des instructions lorsque différentes propriétés sont modifiées pour différentes lignes.
la source
UPDATE
est pratiquement équivalent à unDELETE
+INSERT
(parce que vous créez en fait une nouvelle V ersion de la ligne). Les frais généraux sont élevés et augmentent avec le nombre d' index , surtout si la plupart des colonnes qui les composent sont réellement mises à jour, et l' arborescence (ou autre) utilisée pour représenter l'index a besoin d'un changement significatif. Ce n'est pas le nombre de colonnes mises à jour qui importe, mais la mise à jour d'une partie de colonne d'un index.Réponses:
Je sais que vous êtes principalement préoccupé
UPDATE
et surtout par les performances, mais en tant que responsable de la maintenance "ORM", permettez-moi de vous donner une autre perspective sur le problème de la distinction entre les valeurs "modifiées" , "nulles" et "par défaut" , qui sont trois choses différentes en SQL, mais peut-être une seule chose en Java et dans la plupart des ORM:Traduire votre justification en
INSERT
déclarationsVos arguments en faveur de la batchabilité et de la mise en cache des instructions sont valables de la même manière pour
INSERT
instructions que pour lesUPDATE
instructions. Mais dans le cas d'INSERT
instructions, l'omission d'une colonne de l'instruction a une sémantique différente de celle deUPDATE
. Cela signifie appliquerDEFAULT
. Les deux suivants sont sémantiquement équivalents:Ce n'est pas vrai pour
UPDATE
, où les deux premiers sont sémantiquement équivalents, et le troisième a une signification entièrement différente:La plupart des API clientes de base de données, y compris JDBC, et par conséquent, JPA, ne permettent pas de lier un
DEFAULT
expression à une variable de liaison - principalement parce que les serveurs ne le permettent pas non plus. Si vous souhaitez réutiliser la même instruction SQL pour les raisons de batchability et de mise en cache des instructions susmentionnées, vous devez utiliser l'instruction suivante dans les deux cas (en supposant que(a, b, c)
toutes les colonnes figurent danst
):Et comme ce
c
n'est pas défini, vous lieriez probablement Javanull
à la troisième variable de liaison, car de nombreux ORM ne peuvent pas non plus faire la distinction entreNULL
etDEFAULT
( jOOQ , par exemple étant une exception ici). Ils ne voient que Javanull
et ne savent pas si cela signifieNULL
(comme dans la valeur inconnue) ouDEFAULT
(comme dans la valeur non initialisée).Dans de nombreux cas, cette distinction n'a pas d'importance, mais si votre colonne c utilise l'une des fonctionnalités suivantes, l'instruction est tout simplement erronée :
DEFAULT
clauseRetour aux
UPDATE
déclarationsBien que ce qui précède soit vrai pour toutes les bases de données, je peux vous assurer que le problème de déclenchement est également vrai pour la base de données Oracle. Considérez le SQL suivant:
Lorsque vous exécutez ce qui précède, vous verrez la sortie suivante:
Comme vous pouvez le voir, l'instruction qui met toujours à jour toutes les colonnes déclenche toujours le déclencheur pour toutes les colonnes, tandis que les instructions qui mettent à jour uniquement les colonnes qui ont changé ne déclenchent que les déclencheurs qui écoutent ces modifications spécifiques.
En d'autres termes:
Le comportement actuel d'Hibernate que vous décrivez est incomplet et pourrait même être considéré comme incorrect en présence de déclencheurs (et probablement d'autres outils).
Personnellement, je pense que votre argument d'optimisation du cache de requête est surfait dans le cas de SQL dynamique. Bien sûr, il y aura quelques requêtes supplémentaires dans un tel cache et un peu plus de travail d'analyse à effectuer, mais ce n'est généralement pas un problème pour les
UPDATE
instructions dynamiques , beaucoup moins que pourSELECT
.Le traitement par lots est certainement un problème, mais à mon avis, une seule mise à jour ne devrait pas être normalisée pour mettre à jour toutes les colonnes simplement parce qu'il y a une légère possibilité que l'instruction soit groupable. Il y a de fortes chances que l'ORM puisse collecter des sous-lots d'instructions identiques consécutives et les regrouper au lieu du "lot entier" (dans le cas où l'ORM est même capable de suivre la différence entre "modifié" , "nul" et "par défaut"
la source
DEFAULT
cas d'utilisation peut être résolu par@DynamicInsert
. La situation TRIGGER peut également être résolue à l'aide de contrôles commeWHEN (NEW.b <> OLD.b)
ou simplement basculer vers@DynamicUpdate
.Je pense que la réponse est - c'est compliqué . J'ai essayé d'écrire une preuve rapide en utilisant une
longtext
colonne dans MySQL, mais la réponse n'est pas concluante. Preuve d'abord:Il y a donc une petite différence de temps entre la valeur lente + modifiée et la valeur lente + aucune valeur modifiée. J'ai donc décidé de regarder une autre métrique, qui était des pages écrites:
Il semble donc que le temps a augmenté car il doit y avoir une comparaison pour confirmer que la valeur elle-même n'a pas été modifiée, ce qui dans le cas d'un texte long 1G prend du temps (car il est divisé sur plusieurs pages). Mais la modification elle-même ne semble pas parcourir le journal de rétablissement.
Je soupçonne que si les valeurs sont des colonnes régulières sur la page, la comparaison n'ajoute qu'une petite surcharge. Et en supposant que la même optimisation s'applique, ce ne sont pas des opérations en ce qui concerne la mise à jour.
Réponse plus longue
En fait, je pense que l'ORM ne devrait pas éliminer les colonnes qui ont été modifiées ( mais pas changées ), car cette optimisation a d'étranges effets secondaires.
Tenez compte des éléments suivants dans le pseudo-code:
Le résultat si l'ORM devait "Optimiser" la modification sans changement:
Résultat si l'ORM a envoyé toutes les modifications au serveur:
Le cas de test repose ici sur l'
repeatable-read
isolement (MySQL par défaut), mais une fenêtre de temps existe également pour l'read-committed
isolement où la lecture de session2 a lieu avant la validation de session1.En d'autres termes: l'optimisation n'est sûre que si vous émettez un
SELECT .. FOR UPDATE
pour lire les lignes suivi d'unUPDATE
.SELECT .. FOR UPDATE
n'utilise pas MVCC et lit toujours la dernière version des lignes.Modifier: Assurez-vous que l'ensemble de données du scénario de test était à 100% en mémoire. Résultats de synchronisation ajustés.
la source