Die Funktionsweise der internen Mechanismen von PostgreSQL ermöglicht es, in bestimmten Situationen sehr schnell zu sein und in anderen weniger. Heute betrachten wir ein klassisches Beispiel für den Konflikt zwischen der Funktionsweise der Datenbank und dem, was ein Entwickler damit tut — UPDATE vs MVCC-Prinzipien.
Umreißen wir die Handlung aus :
Wenn eine Zeile mit dem Befehl UPDATE geändert wird, erfolgen tatsächlich zwei Operationen: DELETE und INSERT. Bei der aktuellen Version der Zeile wird xmax auf die Transaktionsnummer gesetzt, die das UPDATE ausgeführt hat. Anschließend wird eine neue Version derselben Zeile erstellt; der Wert von xmin entspricht dem xmax der vorherigen Version.
Nach Abschluss dieser Transaktion werden die alte oder neue Version, je nach COMMIT/ROLLBACK, als „tot“ (dead tuples) oder beim Durchlauf VACUUM in der Tabelle erkannt und bereinigt.

Dies geschieht jedoch nicht sofort, während Probleme mit „Toten“ sehr schnell auftreten können — insbesondere bei mehrfachen oder in einer großen Tabelle. Später könnte man dann feststellen, dass selbst .
#1: I Like To Move It
Angenommen, Ihre Methode in der Geschäftslogik arbeitet einwandfrei, und plötzlich merkt sie, dass das Feld X in einem bestimmten Datensatz aktualisiert werden muss:
UPDATE tbl SET X = WHERE pk = $1;Dann stellt sich während der Ausführung heraus, dass auch das Feld Y aktualisiert werden sollte:
UPDATE tbl SET Y = WHERE pk = $1;… und dann noch Z – warum sich damit aufhalten?
UPDATE tbl SET Z = WHERE pk = $1;Wie viele Versionen dieses Datensatzes haben wir nun in der Datenbank? Aha, 4 Stück! Davon ist eine aktuell, und 3 müssen Sie durch [auto]VACUUM entfernen.
Das brauchen Sie nicht! Nutzen Sie die Aktualisierung aller Felder in einer Anfrage – fast immer lässt sich die Logik des Verfahrens so ändern:
UPDATE tbl SET X = , Y = , Z = WHERE pk = $1;#2: Use IS DISTINCT FROM, Luke!
Also, Sie möchten tatsächlich (zum Beispiel während der Anwendung eines Skripts oder Konverters). Und das Skript enthält etwa dies:
UPDATE tbl SET X = WHERE pk BETWEEN $1 AND $2;Etwa in dieser Form taucht die Anfrage häufig auf und fast immer nicht, um ein neues leeres Feld zu füllen, sondern um einige Fehler in den Daten zu korrigieren. Dabei wird die Korrektheit der bereits vorhandenen Daten überhaupt nicht berücksichtigt – und das ist ein Fehler! Das heißt, der Datensatz wird überschrieben, selbst wenn genau das, was gewünscht war, drinstand – aber warum? Korrigieren wir das:
UPDATE tbl SET X = WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM ; Viele wissen nicht von der Existenz dieses großartigen Operators, deshalb hier eine kurze Zusammenfassung von IS DISTINCT FROM und anderen logischen Operatoren zur Hilfe:

… und ein wenig über Operationen mit komplexen ROW()-Ausdrücken:

#3: А я милого узнаю по… блокировке
Es werden zwei identische parallele Prozesse gestartet,von denen jeder versucht, den Eintrag als „in Bearbeitung“ zu kennzeichnen:
UPDATE tbl SET processing = TRUE WHERE pk = $1;Selbst wenn diese Prozesse unabhängig voneinander handeln, blockiert der zweite Client bei dieser Abfrage, solange die erste Transaktion nicht abgeschlossen ist.
Lösung Nr. 1: Die Aufgabe reduziert sich auf das Vorherige.
Fügen wir einfach wieder hinzu: IS DISTINCT FROM:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;In dieser Form wird die zweite Abfrage in der Datenbank einfach nichts ändern, da dort bereits „alles in Ordnung“ ist – daher wird keine Sperre auftreten. Das Fehlen des Eintrags wird dann im Anwendungsalgorithmus behandelt.
Lösung Nr. 2: Advisory Locks
Ein großes Thema für einen separaten Artikel, in dem man mehr über .
Lösung Nr. 3: sinnlose Aufrufe
Hier sollte auf jeden Fall etwas geschehen. gleichzeitiger Zugriff auf denselben Datensatz? Или вы все-таки накосячили с алгоритмами вызовов бизнес-логики со стороны клиента, например? А если подумать?..
Quelle: habr.com
