PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Können mehrere Threads doppelte Aktualisierungen für eingeschränkte Sätze verursachen?

Ihre angegebenen Garantien gelten in diesem einfachen Fall, aber nicht unbedingt in etwas komplexeren Anfragen. Beispiele finden Sie am Ende der Antwort.

Der einfache Fall

Angenommen, col1 ist eindeutig, hat genau einen Wert "2" oder hat eine stabile Reihenfolge, also jedes UPDATE stimmt mit denselben Zeilen in derselben Reihenfolge überein:

Was bei dieser Abfrage passieren wird, ist, dass die Threads die Zeile mit col=2 finden und alle versuchen, eine Schreibsperre für dieses Tupel zu erlangen. Genau einer davon wird erfolgreich sein. Die anderen blockieren das Warten auf die Übergabe der Transaktion des ersten Threads.

Dieser erste TX wird schreiben, übergeben und eine Zeilenzahl von 1 zurückgeben. Die Übergabe wird die Sperre aufheben.

Die anderen Sender werden erneut versuchen, das Schloss zu greifen. Einer nach dem anderen werden sie erfolgreich sein. Jede Transaktion durchläuft wiederum den folgenden Prozess:

  • Schaffen Sie die Schreibsperre für das umstrittene Tupel.
  • Überprüfen Sie erneut WHERE col=2 Zustand nach Erhalt der Sperre.
  • Die erneute Prüfung zeigt, dass die Bedingung nicht mehr zutrifft, also UPDATE überspringt diese Zeile.
  • Das UPDATE hat keine anderen Zeilen, daher werden keine aktualisierten Zeilen gemeldet.
  • Commit, Freigabe der Sperre für den nächsten TX, der versucht, es zu bekommen.

In diesem einfachen Fall werden die Aktualisierungen durch das Sperren auf Zeilenebene und die erneute Überprüfung der Bedingung effektiv serialisiert. In komplexeren Fällen nicht so sehr.

Das können Sie ganz einfach demonstrieren. Öffnen Sie beispielsweise vier psql-Sitzungen. Sperren Sie im ersten die Tabelle mit BEGIN; LOCK TABLE test; . In den restlichen Sessions läuft identisch UPDATE s - sie blockieren die Sperre auf Tabellenebene. Lösen Sie nun die Sperre durch COMMIT Beginnen Sie Ihre erste Sitzung. Sieh ihnen beim Rennen zu. Nur einer meldet eine Zeilenanzahl von 1, die anderen melden 0. Dies ist leicht zu automatisieren und per Skript zu wiederholen und auf mehr Verbindungen/Threads zu skalieren.

Um mehr zu erfahren, lesen Sie die Regeln für gleichzeitiges Schreiben , Seite 11 von Probleme mit der Parallelität von PostgreSQL - und lesen Sie dann den Rest dieser Präsentation.

Und wenn col1 nicht eindeutig ist?

Wie Kevin in den Kommentaren bemerkte, wenn col nicht eindeutig ist, so dass Sie möglicherweise mehrere Zeilen abgleichen, dann unterschiedliche Ausführungen von UPDATE könnte unterschiedliche Bestellungen erhalten. Dies kann passieren, wenn sie verschiedene Pläne wählen (sagen wir, einer ist ein über PREPARE und EXECUTE und ein anderer ist direkt, oder Sie spielen mit enable_ herum GUCs) oder wenn der Plan, den sie alle verwenden, eine instabile Art von gleichen Werten verwendet. Wenn sie die Zeilen in einer anderen Reihenfolge erhalten, sperrt tx1 ein Tupel, tx2 sperrt ein anderes, und dann versuchen sie jeweils, die bereits gesperrten Tupel des anderen zu sperren. PostgreSQL wird einen von ihnen mit einer Deadlock-Ausnahme abbrechen. Dies ist ein weiterer guter Grund, warum alle Ihr Datenbankcode sollte immer Seien Sie darauf vorbereitet, Transaktionen erneut zu versuchen.

Wenn Sie darauf achten, gleichzeitig UPDATE Wenn Sie immer dieselben Zeilen in derselben Reihenfolge erhalten, können Sie sich immer noch auf das im ersten Teil der Antwort beschriebene Verhalten verlassen.

Frustrierenderweise bietet PostgreSQL UPDATE ... ORDER BY nicht an Es ist also nicht so einfach, sicherzustellen, dass Ihre Updates immer dieselben Zeilen in derselben Reihenfolge auswählen. A SELECT ... FOR UPDATE ... ORDER BY gefolgt von einem separaten UPDATE ist oft am sichersten.

Komplexere Abfragen, Warteschlangensysteme

Wenn Sie Abfragen mit mehreren Phasen, mehreren Tupeln oder anderen Bedingungen als Gleichheit durchführen, können Sie überraschende Ergebnisse erhalten, die sich von den Ergebnissen einer seriellen Ausführung unterscheiden. Insbesondere gleichzeitige Ausführungen von etwas wie:

UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

oder andere Versuche, ein einfaches "Warteschlangen"-System zu bauen, werden *fail* funktioniert wie erwartet. Siehe die PostgreSQL-Dokumentation zur Parallelität und diese Präsentation für weitere Informationen.

Wenn Sie eine datenbankgestützte Arbeitswarteschlange wünschen, gibt es bewährte Lösungen, die alle überraschend komplizierten Sonderfälle handhaben. Eines der beliebtesten ist PgQ . Es gibt ein nützliches PgCon-Papier zum Thema und eine Google-Suche nach 'postgresql queue' ist voller nützlicher Ergebnisse.

Übrigens, statt einer LOCK TABLE Sie können SELECT 1 FROM test WHERE col = 2 FOR UPDATE; verwenden um eine Schreibsperre für genau dieses Tupel zu erhalten. Dadurch werden Aktualisierungen blockiert, aber keine Schreibvorgänge in andere Tupel oder Lesevorgänge blockiert. Dadurch können Sie verschiedene Arten von Parallelitätsproblemen simulieren.