Ich habe eine Postgres-Datenbank, die Details zu Serverclustern enthält, z. B. den Serverstatus ("Aktiv", "Standby" usw.). Aktive Server müssen möglicherweise jederzeit auf einen Standby-Modus umschalten, und es ist mir egal, welcher Standby-Modus im Besonderen verwendet wird.
Ich möchte, dass eine Datenbankabfrage den Status eines Standbys ändert - NUR EINS - und die zu verwendende Server-IP zurückgibt. Die Auswahl kann beliebig sein: Da sich der Status des Servers mit der Abfrage ändert, spielt es keine Rolle, welcher Standby-Modus ausgewählt ist.
Kann ich meine Abfrage auf nur ein Update beschränken?
Folgendes habe ich bisher:
UPDATE server_info SET status = 'active'
WHERE status = 'standby' [[LIMIT 1???]]
RETURNING server_ip;
Postgres gefällt das nicht. Was könnte ich anders machen?
postgresql
update
concurrency
queue
Überlegener Mann
quelle
quelle
Antworten:
Ohne gleichzeitigen Schreibzugriff
Materialisieren Sie eine Auswahl in einem CTE und fügen Sie sie in die
FROM
Klausel des CTE einUPDATE
.Ich hatte ursprünglich eine einfache Unterabfrage hier, aber das kann die
LIMIT
für bestimmte Abfragepläne umgehen, wie Feike betonte:Oder verwenden Sie eine schwach korrelierte Unterabfrage für den einfachen Fall mit
LIMIT
1
. Einfacher, schneller:Mit gleichzeitigem Schreibzugriff
Angenommen, die Standardisolationsstufe
READ COMMITTED
für all dies. Strengere Isolationsstufen (REPEATABLE READ
undSERIALIZABLE
) können weiterhin zu Serialisierungsfehlern führen. Sehen:Fügen Sie unter gleichzeitiger Schreiblast hinzu
FOR UPDATE SKIP LOCKED
, um die Zeile zu sperren, um Rennbedingungen zu vermeiden.SKIP LOCKED
wurde in Postgres 9.5 hinzugefügt , ältere Versionen siehe unten. Das Handbuch:Wenn keine qualifizierende, nicht gesperrte Zeile mehr vorhanden ist, passiert in dieser Abfrage nichts (es wird keine Zeile aktualisiert) und Sie erhalten ein leeres Ergebnis. Für unkritische Vorgänge bedeutet dies, dass Sie fertig sind.
Bei gleichzeitigen Transaktionen sind möglicherweise Zeilen gesperrt, die Aktualisierung wird jedoch nicht abgeschlossen (
ROLLBACK
oder aus anderen Gründen). Um sicherzugehen, führen Sie eine Endkontrolle durch:SELECT
sieht auch gesperrte Zeilen. Wenn dies nicht der Fall isttrue
, werden noch eine oder mehrere Zeilen verarbeitet, und Transaktionen können noch zurückgesetzt werden. (Oder in der Zwischenzeit wurden neue Zeilen hinzugefügt.) Warten Sie ein wenig, und wiederholen Sie dann die beiden Schritte (UPDATE
bis Sie keine Zeile mehr zurückbekommen;SELECT
...), bis Sie erhaltentrue
.Verbunden:
Ohne
SKIP LOCKED
in PostgreSQL 9.4 oder älterGleichzeitige Transaktionen, die versuchen, dieselbe Zeile zu sperren, werden gesperrt, bis die erste ihre Sperre aufhebt.
Wenn die erste zurückgesetzt wurde, übernimmt die nächste Transaktion die Sperre und fährt normal fort. andere in der Warteschlange warten weiter.
Wenn das erste Commit ausgeführt wird, wird die
WHERE
Bedingung erneut ausgewertet, und wenn sie nichtTRUE
mehr vorhanden ist (status
sich geändert hat), gibt der CTE (etwas überraschend) keine Zeile zurück. Nichts passiert. Das ist das gewünschte Verhalten , wenn alle Transaktionen aktualisieren mögen die gleiche Zeile .Aber nicht, wenn jede Transaktion die nächste Zeile aktualisieren möchte . Und da wir nur eine beliebige (oder zufällige ) Zeile aktualisieren möchten , ist es sinnlos, überhaupt zu warten.
Wir können die Situation mit Hilfe von entsperren Beratungssperren :
Auf diese Weise wird die nächste noch nicht gesperrte Zeile aktualisiert. Jede Transaktion erhält eine neue Zeile, mit der gearbeitet werden kann. Ich hatte Hilfe von Czech Postgres Wiki für diesen Trick.
id
ist eine eindeutigebigint
Spalte (oder ein Typ mit einer impliziten Besetzung wieint4
oderint2
).Wenn Advisory-Sperren für mehrere Tabellen in Ihrer Datenbank gleichzeitig verwendet werden, sollten Sie
pg_try_advisory_xact_lock(tableoid::int, id)
- hierid
eindeutiginteger
angeben.Da
tableoid
es sich um einebigint
Menge handelt, kann sie theoretisch überlaufeninteger
. Wenn Sie paranoid genug sind, verwenden Sie(tableoid::bigint % 2147483648)::int
stattdessen - lassen Sie eine theoretische "Hash-Kollision" für die wirklich paranoiden ...Außerdem kann Postgres die
WHERE
Bedingungen in beliebiger Reihenfolge testen . Es könnte vorherpg_try_advisory_xact_lock()
eine Sperre testen und erwerben , was zu zusätzlichen Hinweissperren für nicht verwandte Zeilen führen könnte, wenn dies nicht zutrifft. Verwandte Frage zu SO:status = 'standby'
status = 'standby'
Normalerweise können Sie dies einfach ignorieren. Um sicherzustellen, dass nur qualifizierende Zeilen gesperrt sind, können Sie das / die Prädikat (e) in einem CTE wie oben oder einer Unterabfrage mit dem
OFFSET 0
Hack verschachteln (Inlining wird verhindert) . Beispiel:Oder (billiger für sequentielle Scans) verschachteln Sie die Bedingungen in einer
CASE
Anweisung wie:Doch das
CASE
würde Trick auch Postgres halten verwenden , einen Index aufstatus
. Wenn ein solcher Index verfügbar ist, ist zunächst keine zusätzliche Verschachtelung erforderlich: Bei einem Index-Scan werden nur qualifizierende Zeilen gesperrt.Da Sie nicht sicher sein können, dass in jedem Aufruf ein Index verwendet wird, können Sie einfach:
Das
CASE
ist logisch redundant, dient aber dem besprochenen Zweck.Wenn der Befehl Teil einer langen Transaktion ist, sollten Sie Sperren auf Sitzungsebene in Betracht ziehen, die manuell freigegeben werden können (und müssen). So können Sie entsperren, sobald Sie mit der gesperrten Zeile fertig sind:
pg_try_advisory_lock()
undpg_advisory_unlock()
. Das Handbuch:Verbunden:
quelle