Sperren in PostgreSQL: 1. Beziehungssperren

Die beiden vorherigen Artikelserien haben sich auf Isolation, Multiversionismus und Journaling konzentriert .

In dieser Serie werden wir über Schlösser sprechen. Ich werde mich an diesen Begriff halten, aber in der Literatur kann es auch einen anderen geben: Schloss .

Der Zyklus besteht aus vier Teilen:

  1. Beziehungssperren (dieser Artikel);
  2. Reihenschlösser ;
  3. Sperren anderer Objekte und Prädikatsperren;
  4. Sperrt den Arbeitsspeicher .

Das Material aller Artikel basiert auf administrativen Schulungen , die Pavel pluzanov und ich durchführen, aber nicht wörtlich wiederholen und für nachdenkliches Lesen und unabhängiges Experimentieren gedacht sind.



Allgemeine Informationen zu Schlössern


PostgreSQL verwendet viele verschiedene Mechanismen, die verwendet werden, um etwas zu blockieren (oder zumindest so genannt werden). Daher beginne ich mit den allgemeinsten Worten darüber, warum Schlösser überhaupt benötigt werden, was sie sind und wie sie sich voneinander unterscheiden. Dann werden wir sehen, was von dieser Sorte in PostgreSQL zu finden ist, und erst danach werden wir uns im Detail mit verschiedenen Arten von Sperren befassen.

Sperren werden verwendet, um den gleichzeitigen Zugriff auf freigegebene Ressourcen zu optimieren.

Wettbewerbszugriff bezieht sich auf den gleichzeitigen Zugriff mehrerer Prozesse. Die Prozesse selbst können sowohl parallel (sofern das Gerät dies zulässt) als auch nacheinander im Time-Sharing-Modus ausgeführt werden - dies ist nicht wichtig.

Wenn keine Konkurrenz besteht, sind keine Sperren erforderlich (z. B. erfordert ein gemeinsam genutzter Puffercache Sperren, ein lokaler jedoch nicht).

Vor dem Zugriff auf eine Ressource muss ein Prozess die dieser Ressource zugeordnete Sperre erwerben. Das heißt, wir sprechen von einer bestimmten Disziplin: Alles funktioniert, solange alle Prozesse den festgelegten Regeln für den Zugriff auf eine gemeinsam genutzte Ressource entsprechen. Wenn das DBMS die Sperren verwaltet, überwacht es selbst die Reihenfolge. Wenn die Sperrung durch den Antrag festgelegt wird, liegt diese Verpflichtung bei ihm.

Auf einer niedrigen Ebene wird eine Sperre durch einen Abschnitt des gemeinsam genutzten Speichers dargestellt, in dem auf irgendeine Weise festgestellt wird, ob die Sperre frei oder erfasst ist (und möglicherweise zusätzliche Informationen aufgezeichnet werden: Prozessnummer, Erfassungszeit usw.).

Möglicherweise stellen Sie fest, dass ein solcher gemeinsam genutzter Speicher an sich eine Ressource ist, auf die ein wettbewerbsfähiger Zugriff möglich ist. Wenn wir auf eine niedrigere Ebene gehen, werden wir sehen, dass spezielle Zusatzprimitive (wie Semaphore oder Mutexe), die vom Betriebssystem bereitgestellt werden, verwendet werden, um den Zugriff zu organisieren. Ihre Bedeutung ist, dass der Code, der auf die gemeinsam genutzte Ressource zugreift, jeweils nur in einem Prozess ausgeführt werden sollte. Auf der untersten Ebene werden diese Grundelemente basierend auf atomaren Prozessoranweisungen (wie Test-and-Set oder Compare-and-Swap) implementiert.

Nachdem die Ressource vom Prozess nicht mehr benötigt wird, wird die Sperre aufgehoben, damit andere Benutzer die Ressource verwenden können.

Das Sperren der Sperre ist natürlich nicht immer möglich: Die Ressource wird möglicherweise bereits von einer anderen Person belegt. Dann tritt der Prozess entweder in die Warteschlange ein (wenn der Sperrmechanismus diese Möglichkeit bietet) oder versucht erneut, die Sperre nach einer bestimmten Zeit zu erfassen. Auf die eine oder andere Weise führt dies dazu, dass der Prozess gezwungen ist, im Vorgriff auf die Freigabe der Ressource untätig zu bleiben.

Manchmal ist es möglich, andere nicht blockierende Strategien anzuwenden. Beispielsweise ermöglicht der Multiversionsmechanismus in einigen Fällen, dass mehrere Prozesse gleichzeitig mit verschiedenen Datenversionen arbeiten, ohne sich gegenseitig zu blockieren.

Im Prinzip kann eine geschützte Ressource alles sein, wenn nur diese Ressource eindeutig identifiziert und mit einer Sperradresse abgeglichen werden könnte.

Die Ressource kann beispielsweise das Objekt sein, mit dem das DBMS arbeitet, z. B. eine Datenseite (identifiziert durch den Dateinamen und die Position in der Datei), eine Tabelle (oid im Systemverzeichnis), eine Tabellenzeile (Seite und Offset innerhalb der Seite). Eine Ressource kann eine Struktur im Speicher sein, z. B. eine Hash-Tabelle, ein Puffer usw. (gekennzeichnet durch eine vorab zugewiesene Nummer). Manchmal ist es sogar bequem, abstrakte Ressourcen zu verwenden, die keine physikalische Bedeutung haben (sie werden einfach durch eine eindeutige Nummer identifiziert).

Die Wirksamkeit von Schlössern wird von vielen Faktoren beeinflusst, von denen wir zwei unterscheiden.

  • Granularität (Granularität) ist wichtig, wenn Ressourcen eine Hierarchie bilden.

    Eine Tabelle besteht beispielsweise aus Seiten, die Tabellenzeilen enthalten. Alle diese Objekte können als Ressourcen fungieren. Wenn Prozesse normalerweise nur an wenigen Zeilen interessiert sind und die Sperre auf Tabellenebene festgelegt ist, können andere Prozesse nicht gleichzeitig mit anderen Zeilen arbeiten. Je höher die Granularität ist, desto besser ist daher die Möglichkeit einer Parallelisierung.

    Dies führt jedoch zu einer Erhöhung der Anzahl der Sperren (Informationen darüber müssen gespeichert werden). In diesem Fall kann eine Erhöhung der Ebene (Eskalation) von Sperren angewendet werden: Wenn die Anzahl der granularen Sperren auf niedriger Ebene eine bestimmte Grenze überschreitet, werden sie durch eine Sperre auf einer höheren Ebene ersetzt.
  • Sperren können in verschiedenen Modi erfasst werden .

    Die Namen der Modi können absolut willkürlich sein, nur die Matrix ihrer Kompatibilität untereinander ist wichtig. Ein Modus, der mit keinem Modus (einschließlich mit sich selbst) kompatibel ist, wird normalerweise als exklusiv oder exklusiv bezeichnet. Wenn die Modi kompatibel sind, kann die Sperre von mehreren Prozessen gleichzeitig erfasst werden. Solche Modi werden als gemeinsam genutzt bezeichnet. Im Allgemeinen werden umso mehr Möglichkeiten für Parallelität geschaffen, je mehr verschiedene Modi unterschieden werden können, die miteinander kompatibel sind.

Je nach Nutzungsdauer können Schlösser in lange und kurze Schlösser unterteilt werden.

  • Langzeitsperren werden für eine möglicherweise lange Zeit (normalerweise bis zum Ende der Transaktion) erfasst und beziehen sich meist auf Ressourcen wie Tabellen (Beziehungen) und Zeilen. PostgreSQL verwaltet diese Sperren normalerweise automatisch, der Benutzer hat jedoch eine gewisse Kontrolle über diesen Prozess.

    Lange Sperren zeichnen sich durch eine Vielzahl von Modi aus, so dass möglichst viele gleichzeitige Aktionen an den Daten ausgeführt werden können. Typischerweise gibt es für solche Sperren eine entwickelte Infrastruktur (z. B. Unterstützung von Warteschlangen und Erkennung von Deadlocks) und Überwachungstools, da die Kosten für die Wartung all dieser Annehmlichkeiten im Vergleich zu den Betriebskosten für geschützte Daten immer noch unvergleichlich niedriger sind.
  • Kurzzeitsperren werden für kurze Zeit erfasst (von einigen Prozessoranweisungen bis zu Sekundenbruchteilen) und beziehen sich normalerweise auf Datenstrukturen im gemeinsam genutzten Speicher. PostgreSQL verwaltet solche Sperren vollautomatisch - Sie müssen nur über ihre Existenz Bescheid wissen.

    Kurze Sperren zeichnen sich durch ein Minimum an Modi (exklusiv und gemeinsam genutzt) und eine einfache Infrastruktur aus. In einigen Fällen sind möglicherweise nicht einmal Überwachungstools verfügbar.

PostgreSQL verwendet verschiedene Arten von Sperren.

Sperren auf Objektebene sind langfristige „schwere“ Sperren . Die Ressourcen hier sind Beziehungen und andere Objekte. Wenn das Wort Blockierung ohne Klarstellung im Text erscheint, bedeutet dies eine solche „normale“ Blockierung.

Bei Langzeitsperren fallen Sperren auf Zeilenebene separat auf. Ihre Implementierung unterscheidet sich von anderen Langzeitsperren aufgrund ihrer potenziell großen Anzahl (stellen Sie sich vor, Sie aktualisieren eine Million Zeilen in einer Transaktion). Solche Sperren werden im nächsten Artikel behandelt.

Der dritte Artikel der Reihe befasst sich mit den verbleibenden Sperren auf Objektebene sowie mit Prädikatsperren (da Informationen zu all diesen Sperren auf dieselbe Weise im RAM gespeichert werden).

Kurze Sperren umfassen verschiedene Sperren von RAM-Strukturen . Wir werden sie im letzten Artikel des Zyklus betrachten.

Objekt sperrt


Wir beginnen also mit Sperren auf Objektebene. Hier wird ein Objekt in erster Linie als Relationen verstanden, dh als Tabellen, Indizes, Sequenzen, materialisierte Darstellungen, aber auch als einige andere Entitäten. Diese Sperren schützen Objekte normalerweise davor, gleichzeitig geändert zu werden oder während des Objektwechsels verwendet zu werden, aber auch für andere Zwecke.

Verschwommene Formulierung? Dies liegt daran, dass Sperren aus dieser Gruppe für eine Vielzahl von Zwecken verwendet werden. Was sie verbindet, ist, wie sie angeordnet sind.

Gerät


Die Objektsperren befinden sich im gemeinsam genutzten Speicher des Servers. Ihre Anzahl wird durch das Produkt der Werte zweier Parameter begrenzt: max_locks_per_transaction × max_connections .

Der Sperrpool ist für alle Transaktionen gleich, dh eine Transaktion kann mehr Sperren erfassen als max_locks_per_transaction : Es ist nur wichtig, dass die Gesamtzahl der Sperren im System das festgelegte Limit nicht überschreitet. Der Pool wird beim Start erstellt. Wenn Sie also eine der beiden angegebenen Optionen ändern, müssen Sie den Server neu starten.

Alle Sperren können in der Ansicht pg_locks angezeigt werden.

Wenn die Ressource bereits im inkompatiblen Modus gesperrt ist, wird eine Transaktion, die versucht, diese Ressource zu erfassen, in die Warteschlange gestellt und wartet auf die Freigabe der Sperre. Ausstehende Transaktionen verbrauchen keine Prozessorressourcen: Die entsprechenden Serviceprozesse „schlafen ein“ und werden vom Betriebssystem aktiviert, wenn die Ressource freigegeben wird.

Eine Deadlock- oder Deadlock- Situation ist möglich, in der für eine Transaktion eine von der zweiten Transaktion belegte Ressource erforderlich ist, um fortzufahren, und für die zweite eine von der ersten belegte Ressource erforderlich ist (im Allgemeinen können ein Deadlock und mehr als zwei Transaktionen auftreten). In diesem Fall wird das Warten auf unbestimmte Zeit fortgesetzt, sodass PostgreSQL solche Situationen automatisch erkennt und eine der Transaktionen abbricht, damit andere weiterarbeiten können. (Wir werden im nächsten Artikel mehr über Deadlocks sprechen.)

Objekttypen


Hier ist eine Liste der Arten von Sperren (oder, wenn Sie möchten, der Arten von Objekten), mit denen wir uns in diesem und im nächsten Artikel befassen werden. Die Namen werden gemäß der Spalte locktype in der Ansicht pg_locks angegeben.

  • Beziehung

    Beziehungssperren.
  • Transaktions- ID und VirtualXID

    Blockieren einer Transaktionsnummer (real oder virtuell). Jede Transaktion selbst verfügt über eine exklusive Sperre mit einer eigenen Nummer. Daher können solche Sperren bequem verwendet werden, wenn Sie bis zum Ende einer anderen Transaktion warten müssen.
  • Tupel

    String-Versionssperre. In einigen Fällen wird es verwendet, um die Priorität für mehrere Transaktionen festzulegen, bei denen erwartet wird, dass dieselbe Zeile gesperrt wird.

Wir werden die Diskussion der verbleibenden Arten von Sperren auf den dritten Artikel im Zyklus verschieben. Alle von ihnen werden entweder nur im Ausnahmemodus oder exklusiv und gemeinsam erfasst.

  • verlängern

    Wird verwendet, wenn einer Datei einer Beziehung Seiten hinzugefügt werden.
  • Objekt

    Sperren von Objekten, die keine Beziehungen sind (Datenbanken, Schemas, Abonnements usw.).
  • Seite

    Die Seitensperre wird selten und nur von einigen Arten von Indizes verwendet.
  • Beratung

    Empfohlene Blockierung, vom Benutzer manuell eingestellt.

Beziehungssperren


Um den Kontext nicht zu verlieren, werde ich auf einem solchen Bild die Arten von Schlössern markieren, auf die später noch eingegangen wird.



Modi


Wenn nicht das Wichtigste, dann sicherlich das „verzweigteste“ Blockieren - Blockieren von Beziehungen. Für sie sind bis zu 8 verschiedene Modi definiert. Eine solche Menge ist notwendig, damit die größtmögliche Anzahl von Befehlen, die zu einer Tabelle gehören, gleichzeitig ausgeführt werden kann.

Es macht keinen Sinn, diese Modi auswendig zu lernen oder zu versuchen, die Bedeutung ihrer Namen zu verstehen. Die Hauptsache ist, zur richtigen Zeit eine Matrix vor Ihren Augen zu haben, die zeigt, welche Sperren miteinander in Konflikt stehen. Der Einfachheit halber wird es hier zusammen mit Beispielen von Befehlen wiedergegeben, für die geeignete Sperrstufen erforderlich sind:

SperrmodusWieRSRESueS.SREE.AeBeispiel SQL-Befehle
ZugriffsfreigabeX.AUSWÄHLEN
ZeilenfreigabeX.X.FÜR UPDATE / SHARE AUSWÄHLEN
Zeile exklusivX.X.X.X.INSERT, UPDATE, DELETE
Update exklusiv teilenX.X.X.X.X.VAKUUM, ALTER TABELLE * , INDEX KONZURRENT ERSTELLEN
TeilenX.X.X.X.X.INDEX ERSTELLEN
Zeile exklusiv teilenX.X.X.X.X.X.TRIGGER ERSTELLEN, TABELLE ÄNDERN *
ExklusivX.X.X.X.X.X.X.REFRESH MAT. KONZURRENT ANZEIGEN
Zugriff exklusivX.X.X.X.X.X.X.X.DROP, TRUNCATE, VACUUM FULL, LOCK TABLE, ALTER TABLE * , REFRESH MAT. ANSICHT

Einige Kommentare:

  • Die ersten 4 Modi ermöglichen gleichzeitige Datenänderungen in der Tabelle, die nächsten 4 nicht.
  • Der erste Modus (Access Share) ist der schwächste und mit allen anderen als dem letzten kompatibel (Access Exclusive). Dieser letzte Modus ist exklusiv und mit keinem Modus kompatibel.
  • Der Befehl ALTER TABLE verfügt über viele Optionen, für die unterschiedliche Sperrstufen erforderlich sind. Daher wird dieser Befehl in der Matrix in verschiedenen Zeilen angezeigt und mit einem Sternchen markiert.

Zum Beispiel


Geben Sie ein Beispiel. Was passiert, wenn ich den Befehl CREATE INDEX ausführe?

In der Dokumentation finden wir, dass dieser Befehl die Sperre in den Freigabemodus setzt. Gemäß der Matrix stellen wir fest, dass der Befehl mit sich selbst (dh Sie können gleichzeitig mehrere Indizes erstellen) und mit Lesebefehlen kompatibel ist. Somit funktionieren die SELECT-Befehle weiterhin, aber die UPDATE-, DELETE- und INSERT-Befehle werden blockiert.

Und umgekehrt - unvollständige Transaktionen, die Daten in der Tabelle ändern, blockieren die Operation des Befehls CREATE INDEX. Daher gibt es eine Variante des Befehls - INDEX CONCURRENTLY ERSTELLEN. Es funktioniert länger (und kann sogar fehlerhaft sein), ermöglicht jedoch gleichzeitige Datenänderungen.

Dies kann in der Praxis gesehen werden. Für Experimente verwenden wir die aus dem ersten Zyklus bekannte Tabelle der Bankkonten, in der wir die Kontonummer und den Betrag speichern.

=> CREATE TABLE accounts( acc_no integer PRIMARY KEY, amount numeric ); => INSERT INTO accounts VALUES (1,1000.00), (2,2000.00), (3,3000.00); 

Starten Sie in der zweiten Sitzung die Transaktion. Wir benötigen eine Serviceprozessnummer.

 | => SELECT pg_backend_pid(); 
 | pg_backend_pid | ---------------- | 4746 | (1 row) 

Welche Sperren hält die neu gestartete Transaktion? Wir schauen in pg_locks:

 => SELECT locktype, relation::REGCLASS, virtualxid AS virtxid, transactionid AS xid, mode, granted FROM pg_locks WHERE pid = 4746; 
  locktype | relation | virtxid | xid | mode | granted ------------+----------+---------+-----+---------------+--------- virtualxid | | 5/15 | | ExclusiveLock | t (1 row) 

Wie ich bereits sagte, enthält eine Transaktion immer eine exklusive (ExclusiveLock) Sperre ihrer eigenen Nummer, in diesem Fall eine virtuelle. Es gibt keine weiteren Sperren für diesen Prozess.

Aktualisieren Sie nun die Tabellenzeile. Wie wird sich die Situation ändern?

 | => UPDATE accounts SET amount = amount + 100 WHERE acc_no = 1; 

 => \g 
  locktype | relation | virtxid | xid | mode | granted ---------------+---------------+---------+--------+------------------+--------- relation | accounts_pkey | | | RowExclusiveLock | t relation | accounts | | | RowExclusiveLock | t virtualxid | | 5/15 | | ExclusiveLock | t transactionid | | | 529404 | ExclusiveLock | t (4 rows) 

Jetzt gibt es Sperren für die veränderbare Tabelle und den Index (erstellt für den Primärschlüssel), die vom Befehl UPDATE verwendet werden. Beide Sperren werden im RowExclusiveLock-Modus ausgeführt. Zusätzlich wurde eine exklusive Sperrung der realen Transaktionsnummer hinzugefügt (die angezeigt wurde, sobald die Transaktion begann, Daten zu ändern).

In einer anderen Sitzung versuchen wir nun, einen Index für eine Tabelle zu erstellen.

 || => SELECT pg_backend_pid(); 
 || pg_backend_pid || ---------------- || 4782 || (1 row) 
 || => CREATE INDEX ON accounts(acc_no); 

Der Befehl friert in Erwartung der Freigabe der Ressource ein. Welche Art von Schloss versucht sie zu erobern? Überprüfen Sie:

 => SELECT locktype, relation::REGCLASS, virtualxid AS virtxid, transactionid AS xid, mode, granted FROM pg_locks WHERE pid = 4782; 
  locktype | relation | virtxid | xid | mode | granted ------------+----------+---------+-----+---------------+--------- virtualxid | | 6/15 | | ExclusiveLock | t relation | accounts | | | ShareLock | f (2 rows) 

Wir sehen, dass die Transaktion versucht, die Tabellensperre im ShareLock-Modus zu erhalten, dies jedoch nicht kann (gewährt = f).

Es ist praktisch, die Nummer des Blockierungsprozesses und im Allgemeinen mehrere Nummern mithilfe der in Version 9.6 angegebenen Funktion zu ermitteln (zuvor musste ich Schlussfolgerungen ziehen, indem ich den gesamten Inhalt von pg_locks sorgfältig betrachtete):

 => SELECT pg_blocking_pids(4782); 
  pg_blocking_pids ------------------ {4746} (1 row) 

Um die Situation zu verstehen, können Sie Informationen zu den Sitzungen abrufen, einschließlich der gefundenen Zahlen:

 => SELECT * FROM pg_stat_activity WHERE pid = ANY(pg_blocking_pids(4782)) \gx 
 -[ RECORD 1 ]----+------------------------------------------------------------ datid | 16386 datname | test pid | 4746 usesysid | 16384 usename | student application_name | psql client_addr | client_hostname | client_port | -1 backend_start | 2019-08-07 15:02:53.811842+03 xact_start | 2019-08-07 15:02:54.090672+03 query_start | 2019-08-07 15:02:54.10621+03 state_change | 2019-08-07 15:02:54.106965+03 wait_event_type | Client wait_event | ClientRead state | idle in transaction backend_xid | 529404 backend_xmin | query | UPDATE accounts SET amount = amount + 100 WHERE acc_no = 1; backend_type | client backend 

Nach Abschluss der Transaktion werden die Sperren aufgehoben und der Index erstellt.

 | => COMMIT; 
 | COMMIT 

 || CREATE INDEX 

In der Warteschlange! ..


Um sich besser vorstellen zu können, wozu das Auftreten einer inkompatiblen Sperre führt, werden wir sehen, was passiert, wenn der Befehl VACUUM FULL während des Systembetriebs ausgeführt wird.

Lassen Sie den Befehl SELECT zuerst in unserer Tabelle ausgeführt werden. Sie erhält eine Sperre für die schwächste Stufe von Access Share. Um die Freigabezeit der Sperre zu steuern, führen wir diesen Befehl innerhalb der Transaktion aus. Bis zum Ende der Transaktion wird die Sperre nicht freigegeben. In der Realität können mehrere Befehle die Tabelle lesen (und ändern), und einige der Abfragen können eine Weile dauern.

 => BEGIN; => SELECT * FROM accounts; 
  acc_no | amount --------+--------- 2 | 2000.00 3 | 3000.00 1 | 1100.00 (3 rows) 
 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+-----------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} (1 row) 

Anschließend führt der Administrator den Befehl VACUUM FULL aus, für den eine Zugriffssperrung auf exklusiver Ebene erforderlich ist, die auch mit Access Share nicht kompatibel ist. (Der Befehl LOCK TABLE erfordert ebenfalls dieselbe Sperre.) Die Transaktionswarteschlangen.

 | => BEGIN; | => LOCK TABLE accounts; --  VACUUM FULL 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} relation | AccessExclusiveLock | f | 4746 | {4710} (2 rows) 

Die Anwendung gibt jedoch weiterhin Anforderungen aus, und jetzt wird der Befehl SELECT im System angezeigt. Rein theoretisch hätte sie "ausrutschen" können, während VACUUM FULL wartet, aber nein - sie nimmt ehrlich gesagt einen Platz in der Warteschlange für VACUUM FULL ein.

 || => SELECT * FROM accounts; 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} relation | AccessExclusiveLock | f | 4746 | {4710} relation | AccessShareLock | f | 4782 | {4746} (3 rows) 

Nachdem die erste Transaktion mit dem Befehl SELECT abgeschlossen und die Sperre aufgehoben wurde, beginnt der Befehl VACUUM FULL (den wir mit dem Befehl LOCK TABLE simuliert haben).

 => COMMIT; 
 COMMIT 

 | LOCK TABLE 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessExclusiveLock | t | 4746 | {} relation | AccessShareLock | f | 4782 | {4746} (2 rows) 

Und erst wenn VACUUM FULL seine Arbeit abgeschlossen und die Sperre aufgehoben hat, können alle in der Warteschlange gesammelten Befehle (in unserem Beispiel SELECT) die entsprechenden Sperren erfassen (Access Share) und ausführen.

 | => COMMIT; 
 | COMMIT 

 || acc_no | amount || --------+--------- || 2 | 2000.00 || 3 | 3000.00 || 1 | 1100.00 || (3 rows) 

Somit kann ein ungenauer Befehl den Betrieb des Systems für eine Zeit lähmen, die erheblich länger ist als die Ausführungszeit des Befehls selbst.

Überwachungstools


Natürlich sind Schlösser für den korrekten Betrieb notwendig, können aber zu unerwünschten Erwartungen führen. Solche Erwartungen können überwacht werden, um ihre Ursache zu verstehen und wenn möglich zu beseitigen (z. B. durch Ändern des Anwendungsalgorithmus).

Wir haben bereits eine Möglichkeit kennengelernt: Im Moment einer langen Sperre können wir eine Anforderung an die Ansicht pg_locks ausführen, die sperrbaren und blockierenden Transaktionen (Funktion pg_blocking_pids) anzeigen und sie mit pg_stat_activity entschlüsseln.

Eine andere Möglichkeit besteht darin, den Parameter log_lock_waits zu aktivieren. In diesem Fall werden Informationen im Nachrichtenprotokoll des Servers angezeigt, wenn die Transaktion länger als deadlock_timeout gewartet hat (obwohl der Parameter für Deadlocks verwendet wird, handelt es sich um normale Erwartungen).

Lass es uns versuchen.

 => ALTER SYSTEM SET log_lock_waits = on; => SELECT pg_reload_conf(); 

Der Standardparameterwert für deadlock_timeout beträgt eine Sekunde:

 => SHOW deadlock_timeout; 
  deadlock_timeout ------------------ 1s (1 row) 

Spielen Sie das Schloss.

 => BEGIN; => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 
 UPDATE 1 

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

Der zweite UPDATE-Befehl erwartet eine Sperre. Warten Sie eine Sekunde und schließen Sie die erste Transaktion ab.

 => SELECT pg_sleep(1); => COMMIT; 
 COMMIT 

Jetzt kann die zweite Transaktion abgeschlossen werden.

 | UPDATE 1 
 | => COMMIT; 
 | COMMIT 

Und alle wichtigen Informationen kamen ins Tagebuch:

 postgres$ tail -n 7 /var/log/postgresql/postgresql-11-main.log 
 2019-08-07 15:26:30.827 MSK [5898] student@test LOG: process 5898 still waiting for ShareLock on transaction 529427 after 1000.186 ms 2019-08-07 15:26:30.827 MSK [5898] student@test DETAIL: Process holding the lock: 5862. Wait queue: 5898. 2019-08-07 15:26:30.827 MSK [5898] student@test CONTEXT: while updating tuple (0,4) in relation "accounts" 2019-08-07 15:26:30.827 MSK [5898] student@test STATEMENT: UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 
 2019-08-07 15:26:30.836 MSK [5898] student@test LOG: process 5898 acquired ShareLock on transaction 529427 after 1009.536 ms 2019-08-07 15:26:30.836 MSK [5898] student@test CONTEXT: while updating tuple (0,4) in relation "accounts" 2019-08-07 15:26:30.836 MSK [5898] student@test STATEMENT: UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

Fortsetzung folgt .

Source: https://habr.com/ru/post/de462877/


All Articles