Beim Aktualisieren einer Zeile geben viele ORM-Tools eine UPDATE-Anweisung aus, mit der jede Spalte festgelegt wird, die dieser bestimmten Entität zugeordnet ist .
Der Vorteil ist, dass Sie die Aktualisierungsanweisungen problemlos stapeln können, da die UPDATE
Anweisung unabhängig davon, welches Entitätsattribut Sie ändern, identisch ist. Darüber hinaus können Sie auch serverseitiges und clientseitiges Anweisungs-Caching verwenden.
Wenn ich also eine Entität lade und nur eine einzelne Eigenschaft festlege:
Post post = entityManager.find(Post.class, 1L);
post.setScore(12);
Alle Spalten werden geändert:
UPDATE post
SET score = 12,
title = 'High-Performance Java Persistence'
WHERE id = 1
Sollte title
die DB nun, vorausgesetzt, wir haben auch einen Index für die Eigenschaft, nicht erkennen, dass sich der Wert ohnehin nicht geändert hat?
In diesem Artikel sagt Markus Winand:
Die Aktualisierung aller Spalten zeigt dasselbe Muster, das wir bereits in den vorherigen Abschnitten beobachtet haben: Die Antwortzeit wächst mit jedem zusätzlichen Index.
Ich frage mich, warum das so ist, da die Datenbank die zugehörige Datenseite von der Festplatte in den Speicher lädt und so herausfinden kann, ob ein Spaltenwert geändert werden muss oder nicht.
Selbst für Indizes wird kein Neuausgleich durchgeführt, da sich die Indexwerte für die Spalten, die sich nicht geändert haben, nicht ändern, sie jedoch im UPDATE enthalten waren.
Müssen die B + Tree-Indizes, die den redundanten unveränderten Spalten zugeordnet sind, ebenfalls navigiert werden, nur damit die Datenbank erkennt, dass der Blattwert immer noch derselbe ist?
Natürlich können Sie mit einigen ORM-Tools nur die geänderten Eigenschaften AKTUALISIEREN:
UPDATE post
SET score = 12,
WHERE id = 1
Diese Art von UPDATE profitiert jedoch möglicherweise nicht immer von Stapelaktualisierungen oder Anweisungs-Caching, wenn verschiedene Eigenschaften für verschiedene Zeilen geändert werden.
quelle
UPDATE
praktisch gleichbedeutend mit einemDELETE
+INSERT
(weil Sie tatsächlich eine neue erstellen V ersion der Reihe). Der Overhead ist hoch und wächst mit der Anzahl der Indizes , insbesondere wenn viele der Spalten, aus denen sie bestehen, tatsächlich aktualisiert werden und der Baum (oder was auch immer), der zur Darstellung des Index verwendet wird, eine erhebliche Änderung erfordert. Es ist nicht die Anzahl der Spalten, die aktualisiert werden, sondern ob Sie einen Spaltenteil eines Indexes aktualisieren.Antworten:
Ich weiß, dass Sie sich
UPDATE
hauptsächlich um die Leistung und vor allem um die Leistung kümmern. Lassen Sie mich jedoch als Mitbetreuer von ORM eine weitere Perspektive auf das Problem der Unterscheidung zwischen den Werten "changed" , "null" und "default" geben drei verschiedene Dinge in SQL, aber möglicherweise nur eines in Java und in den meisten ORMs:Übersetzen Sie Ihre Begründung in
INSERT
AussagenIhre Argumente für die Stapelbarkeit und die Zwischenspeicherbarkeit von Anweisungen gelten in gleicher Weise für
INSERT
Anweisungen wie fürUPDATE
Anweisungen. BeiINSERT
Anweisungen hat das Weglassen einer Spalte in der Anweisung eine andere Semantik als inUPDATE
. Es bedeutet, sich zu bewerbenDEFAULT
. Die folgenden zwei sind semantisch äquivalent:Dies trifft nicht zu
UPDATE
, wenn die ersten beiden semantisch äquivalent sind und die dritte eine ganz andere Bedeutung hat:Die meisten Datenbank-Client-APIs, einschließlich JDBC und folglich JPA, lassen das Binden eines
DEFAULT
Ausdrucks an eine Bindungsvariable nicht zu - hauptsächlich, weil die Server dies ebenfalls nicht zulassen. Wenn Sie dieselbe SQL-Anweisung aus den oben genannten Gründen der Stapelbarkeit und der Zwischenspeicherbarkeit von Anweisungen wiederverwenden möchten, verwenden Sie in beiden Fällen die folgende Anweisung (vorausgesetzt,(a, b, c)
alle Spalten sind int
):Und da dies
c
nicht festgelegt ist, würden Sie Java wahrscheinlichnull
an die dritte Bindungsvariable binden, da viele ORMs auch nicht zwischenNULL
und unterscheiden könnenDEFAULT
( JOOQ ist hier beispielsweise eine Ausnahme). Sie sehen nur Javanull
und wissen nicht, ob diesNULL
(wie im unbekannten Wert) oderDEFAULT
(wie im nicht initialisierten Wert) bedeutet.In vielen Fällen spielt diese Unterscheidung keine Rolle. Wenn Ihre Spalte c jedoch eine der folgenden Funktionen verwendet, ist die Aussage einfach falsch :
DEFAULT
KlauselZurück zu den
UPDATE
AussagenObwohl das oben Gesagte für alle Datenbanken zutrifft, kann ich Ihnen versichern, dass das Auslöserproblem auch für die Oracle-Datenbank zutrifft. Betrachten Sie das folgende SQL:
Wenn Sie den obigen Befehl ausführen, wird die folgende Ausgabe angezeigt:
Wie Sie sehen, löst die Anweisung, die immer alle Spalten aktualisiert, immer den Auslöser für alle Spalten aus, während die Anweisungen, die nur geänderte Spalten aktualisieren, nur die Auslöser auslösen, die auf solche spezifischen Änderungen warten.
Mit anderen Worten:
Das aktuelle Verhalten von Hibernate, das Sie beschreiben, ist unvollständig und kann bei Vorhandensein von Triggern (und wahrscheinlich anderen Tools) sogar als falsch angesehen werden.
Ich persönlich denke, dass Ihr Argument zur Optimierung des Abfrage-Cache im Fall von dynamischem SQL überbewertet ist. Sicher, in einem solchen Cache gibt es ein paar mehr Abfragen und ein bisschen mehr Parsing-Arbeit, aber dies ist normalerweise kein Problem für dynamische
UPDATE
Anweisungen, viel weniger als fürSELECT
.Batching ist sicherlich ein Problem, aber meiner Meinung nach sollte ein einzelnes Update nicht normalisiert werden, um alle Spalten zu aktualisieren, nur weil die Möglichkeit gering ist, dass die Anweisung Batch-fähig ist. Möglicherweise kann der ORM Untergruppen aufeinanderfolgender identischer Anweisungen sammeln und diese anstelle der "gesamten Gruppe" stapeln (falls der ORM sogar in der Lage ist, den Unterschied zwischen "geändert" , "null" und "Standard" zu verfolgen ).
quelle
DEFAULT
Anwendungsfall kann durch adressiert werden@DynamicInsert
. Die TRIGGER-Situation kann auch mit Checks wieWHEN (NEW.b <> OLD.b)
oder einfach mit Switch angesprochen werden@DynamicUpdate
.Ich denke die Antwort ist - es ist kompliziert . Ich habe versucht, einen schnellen Beweis mit einer
longtext
Spalte in MySQL zu schreiben , aber die Antwort ist ein wenig unschlüssig. Beweis zuerst:Es gibt also einen kleinen Zeitunterschied zwischen langsam + geändertem Wert und langsam + keinem geänderten Wert. Deshalb habe ich mich für eine andere Metrik entschieden, die Seiten umfasst:
Es sieht also so aus, als hätte sich die Zeit erhöht, da ein Vergleich durchgeführt werden muss, um zu bestätigen, dass der Wert selbst nicht geändert wurde. Dies nimmt im Fall eines 1-G-Langtexts Zeit in Anspruch (da er auf mehrere Seiten aufgeteilt ist). Die Änderung selbst scheint jedoch das Redo-Log nicht zu durchlaufen.
Ich vermute, dass der Vergleich, wenn es sich bei den Werten um reguläre Spalten handelt, die sich in der Seite befinden, nur einen geringen Mehraufwand verursacht. Unter der Annahme, dass die gleiche Optimierung angewendet wird, handelt es sich bei dem Update um No-Ops.
Längere Antwort
Ich denke eigentlich, das ORM sollte keine Spalten entfernen , die modifiziert ( aber nicht verändert ) wurden, da diese Optimierung seltsame Nebenwirkungen hat.
Beachten Sie im Pseudocode Folgendes:
Das Ergebnis, wenn der ORM die Änderung ohne Änderung "optimieren" würde:
Das Ergebnis, wenn der ORM alle Änderungen an den Server gesendet hat:
Der Testfall basiert hier auf der
repeatable-read
Isolation (MySQL-Standard), es gibt jedoch auch ein Zeitfenster für dieread-committed
Isolation, in dem der Lesevorgang für Sitzung2 vor dem Festschreiben von Sitzung1 erfolgt.Anders ausgedrückt: Die Optimierung ist nur dann sicher, wenn Sie ein ausgeben
SELECT .. FOR UPDATE
, um die Zeilen gefolgt von einem zu lesenUPDATE
.SELECT .. FOR UPDATE
verwendet MVCC nicht und liest immer die neueste Version der Zeilen.Bearbeiten: Es wurde sichergestellt, dass sich der Testfalldatensatz zu 100% im Speicher befindet. Angepasste Timing-Ergebnisse.
quelle