Aktualisiert das Aktualisieren einer Zeile mit demselben Wert tatsächlich die Zeile?


27

Ich habe eine leistungsbezogene Frage. Angenommen, ich habe einen Benutzer mit dem Vornamen Michael. Nehmen Sie die folgende Abfrage:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

Führt die Abfrage das Update tatsächlich aus, obwohl es auf denselben Wert aktualisiert wird? Wenn ja, wie verhindere ich das?


1
Warum sollten Sie eine Anweisung ausführen und gleichzeitig erwarten, dass sie nicht ausgeführt wird?
Max Vernon

@MaxVernon Ruby on Rails 'ORM aktualisiert den Datensatz nicht, daher war ich neugierig, ob PostgreSQL dasselbe getan hat.
OneSneakyMofo

1
Ich würde vorschlagen, wenn Ruby on Rails dies tut, wird wahrscheinlich zuerst eine Auswahl getroffen, um zu sehen, ob die Zeile aktualisiert werden muss.
Max Vernon

Antworten:


34

Aufgrund des MVCC-Modells von Postgres und gemäß den Regeln von SQL UPDATEschreibt a eine neue Zeilenversion für jede Zeile, die in der WHEREKlausel nicht ausgeschlossen ist.

Dies wirkt sich direkt und indirekt mehr oder weniger erheblich auf die Leistung aus. "Leere Aktualisierungen" verursachen die gleichen Kosten pro Zeile wie alle anderen Aktualisierungen. Sie lösen Trigger (falls vorhanden) wie jedes andere Update aus, müssen WAL-protokolliert sein und produzieren tote Zeilen, die die Tabelle aufblähen und VACUUMwie jedes andere Update mehr Arbeit für später verursachen .

Indexeinträge und TOASTed- Spalten, in denen keine der beteiligten Spalten geändert wird, können gleich bleiben, dies gilt jedoch für alle aktualisierten Zeilen. Verbunden:

Es ist fast immer eine gute Idee, solche leeren Updates auszuschließen (wenn tatsächlich die Möglichkeit besteht, dass dies passiert). Sie haben in Ihrer Frage keine Tabellendefinition angegeben (was immer eine gute Idee ist). Wir müssen davon ausgehen, first_namedass NULL sein kann (was für einen "Vornamen" nicht überraschend wäre), daher muss die Abfrage einen NULL-sicheren Vergleich verwenden :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Wenn first_name IS NULLvor dem Update ein Test mit nur first_name <> 'Michael'NULL ergibt, wird die Zeile vom Update ausgeschlossen. Hinterhältiger Fehler. Wenn die Spalte definiert istNOT NULL , verwenden Sie die einfache Gleichheitsprüfung, da dies etwas billiger ist.

Verbunden:


1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameAber müssten sie nicht aktualisiert werden, um auf die neue Position der Zeile zu verweisen?
DVTAN

1
@dtgq: Nicht bei HOT-Aktualisierungen, bei denen der Index auf den alten Speicherort verweist und Heap-Abrufe die HOT-Kette durchlaufen müssen, um das Live-Tupel zu erhalten. Ich habe oben Links zu weiteren Erklärungen hinzugefügt.
Erwin Brandstetter

1
Was ist mit MVCC fordert ein Noop-Update, um ein neues Tupel zu schreiben?
Jberryman

@jberryman: Ich bin mir nicht sicher, ob ich das verstehe. In jedem Fall stellen Sie Ihre Frage bitte als neue Frage . Sie können immer einen Link zu diesem für den Kontext erstellen. Und Sie können hier einen Kommentar hinterlassen, um zurück zu verlinken (und meine Aufmerksamkeit zu bekommen).
Erwin Brandstetter

2
@jberryman: Ich kenne eigentlich nicht die Gründe, warum das Projekt so gelaufen ist . Das ist schon lange her. Aber ich nehme an, es wäre unnötig teuer, jede Zeile auf Gleichheit zu prüfen und einen separaten Codepfad für unveränderte Zeilen zu haben. Der Umgang mit Transaktions-IDs wäre komplizierter - spezielle Gehäuse für rollback, Snapshot-Handling, Lock-Management, WAL, und was nicht ...
Erwin Brandstetter

4

ORMs wie Ruby on Rail bieten eine verzögerte Ausführung an, bei der ein Datensatz als geändert markiert wird (oder nicht) und die Änderung dann bei Bedarf oder Aufruf an die Datenbank übermittelt wird.

PostgreSQL ist eine Datenbank und kein ORM. Es hätte die Leistung verringert, wenn es einige Zeit gedauert hätte, zu überprüfen, ob ein neuer Wert mit dem aktualisierten Wert in Ihrer Abfrage übereinstimmt.

Daher wird der Wert unabhängig davon aktualisiert, ob er dem neuen Wert entspricht oder nicht.

Wenn Sie dies verhindern möchten, können Sie Code verwenden, wie ihn Max Vernon in seiner Antwort vorgeschlagen hat.


2

Sie könnten einfach die whereKlausel hinzufügen :

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Wenn first_nameals definiert ist NOT NULL, kann das OR first_name IS NULLTeil entfernt werden.

Die Bedingung:

(first_name <> 'Michael' OR first_name IS NULL)

kann auch eleganter geschrieben werden als (in Erwins Antwort):

first_name IS DISTINCT FROM 'Michael'

Wenn Sie nicht wissen, ob die Spalte NULL sein kann, könnte dies einen hinterhältigen Fehler verursachen.
Erwin Brandstetter

1
@ErwinBrandstetter Ich habe die Antwort aktualisiert - dann habe ich den Kommentar und Ihre Antwort gesehen!
ypercubeᵀᴹ

Vielen Dank für die Bearbeitung, @ypercube - und für den Kommentar zu NULL@erwin
Max Vernon

1

Aus Datenbanksicht

Die Antwort auf Ihre Frage lautet JA. Das Update wird durchgeführt. Die Datenbank überprüft nicht den vorherigen Wert, sondern setzt nur den neuen Wert.

Da dies im Speicher geschieht (und erst in die Datendateien geschrieben wird, nachdem ein Commit ausgegeben wurde), ist die Leistung kein Problem.

Aus Sicht des ORM

Normalerweise haben Sie ein Objekt, das eine einzelne Zeile der Datenbank darstellt (es kann sehr viel komplexer sein, aber lassen Sie es uns einfach halten). Dieses Objekt wird im Arbeitsspeicher verwaltet (auf App-Serverebene), und nur die letzte festgeschriebene Version dieses Objekts gelangt zu einem bestimmten Zeitpunkt tatsächlich in die Datenbank.

Das könnte das unterschiedliche Verhalten erklären.

Vergleichen wir nun ein Frachtschiff nicht mit einem 3D-Drucker. Die Tatsache, dass Sie 3D-Drucker mit Frachtschiffen versenden können, bedeutet nicht, dass es einen Vergleich zwischen ihnen geben könnte.

Genießen!

Ich hoffe das hat einige Konzepte geklärt.


4
Leistung ist und Problem. Jedes Update muss auf die Festplatte geschrieben werden (das Protokoll und die Tabelle).
ypercubeᵀᴹ

Dies hängt vom tatsächlich verwendeten RDBMS ab. Die meisten von ihnen schreiben jedoch nicht jedes einzelne Update fest, sondern nur den letzten festgeschriebenen Block, den sie im Speicher haben. Sie lesen oder schreiben niemals eine einzelne Zeile in einer Datenbank. Sie lesen / schreiben Blöcke und behalten sie im Speicher, bis Sie sie löschen müssen, um einen neuen Block an der gleichen Stelle abzulegen. Im Speicher wird nicht jede Änderung in einer Zeile auf die Festplatte geschrieben, sondern nur der Blockinhalt, wenn der "Datenbankschreiber" -Prozess signalisiert wird, diesen Speicherblock in eine Datendatei zu kopieren. Also, nein ... Ist kein Problem, es sei denn, Ihre Anwendung hält den Block zu lange nicht fest.
Silvarion,

1
Die Frage ist nach Postgres, nicht nach einem beliebigen DBMS. Und während die Aktualisierungen nicht alle einzeln geschrieben werden müssen, muss jeder Schreibvorgang in der Datenbank in das Protokoll geschrieben werden. Wie überlebt das DBMS einen Systemabsturz, wenn eine Änderung nicht in den dauerhaften Speicher geschrieben wird?
ypercubeᵀᴹ

Ja, es schreibt in die Protokolle, auch während der Checkpoints. Es sollte kein Problem sein, es sei denn, Sie haben eine unglaublich große Anzahl von gleichzeitigen Benutzern. Protokolle werden ebenfalls stapelweise geschrieben. Ich denke, wir reden über Server. Wenn es sich um eine Postgres-Datenbank in einem Laptop mit einer 5400-U / min-Festplatte handelt, haben Sie immer Leistungsprobleme. Die endgültige Antwort wäre also die erste ... Es kommt auf zu viele Dinge an.
Silvarion
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.