Einführung
Zwei Schalter verkaufen Plätze in einem Kreativworkshop. Ein Gast erwartet einen bestätigten Platz, keinen Zähler, der lediglich nicht negativ aussieht. Du erstellst zuerst eine normale Gruppenbuchung und reproduzierst dann zwei Bestätigungen für einen einzigen Platz in einem entbehrlichen Übungsworkshop. Abschließend schützt du diesen Platz und lässt einen anderen Workshop weiterhin Gäste bedienen.
Du benötigst grundlegende Terminalnavigation und PostgreSQL SELECT, WHERE, INSERT und UPDATE. Transaktionen, Verbindungsidentität, Zeilensperren, RETURNING und Fehlerbehebung werden hier erklärt. PostgreSQL und synthetische Daten sind vorbereitet. Workshop Desk ist eine Shell; Desk A und Desk B sind separate native psql-Verbindungen zu derselben Datenbank auf einer Maschine. Lass alle drei Tabs geöffnet. Texte und Beschriftungen in der Laufzeitumgebung sind Englisch. Lesen und gewöhnliche Fehler sind in der vom Autor geschätzten Dauer enthalten; dies ist keine Zeitstudie mit Anfängern.
Füge nur die benannten Testbuchungen hinzu und aktualisiere das places_left ihrer Workshops. Die Wettlaufübung erlaubt ausdrücklich das Löschen deiner eigenen Testbuchungen. Bewahre ursprüngliche Buchungen, Workshop-IDs, Titel, Kapazitäten, Notizen, Tabellendefinitionen und vorbereitete Werkzeuge. Ändere keine Servereinstellungen und erstelle keine Tabellen. Diese Übung verwendet PostgreSQLs Isolation READ COMMITTED auf einer Datenbank; Zahlungen, Wiederholungsversuche und Deadlocks über mehrere Workshops liegen außerhalb ihres Umfangs.
Eine normale Gruppenbuchung vornehmen
In diesem Schritt verknüpfst du eine tatsächliche Gruppenbestätigung mit der Anzahl der Plätze, die sie verbraucht.
Beginne in Workshop Desk an der Shell. ./ führt ein Programm aus dem aktuellen Ordner aus. desk status liest die tatsächlichen Tabellen; es repariert sie nicht. Sein erstes Ergebnis listet workshop_id|title|capacity|places_left|booked_seats auf. Das nächste listet request_code|workshop_id|seats|desk auf. Die senkrechten Striche trennen Felder.
./desk status
Workshop 101 hat Kapazität 5, einen ursprünglich gebuchten Platz und 4 freie Plätze. Workshop 102 hat einen Platz und keine Buchungen. Lass original-family in Workshop 101 exakt unverändert.
Wechsle zu Desk A. Seine Eingabeaufforderung Desk A workshops=> bedeutet, dass du dich in PostgreSQLs Client psql befindest, verbunden mit der Datenbank workshops. Gib hier SQL ein, keine Shell-Befehle. Ein Semikolon beendet eine SQL-Anweisung. Eine Verbindung ist ein Gespräch mit dem Datenbankserver; pg_backend_pid() identifiziert ihren Serverprozess. Ein anderer Wert in Desk B kennzeichnet eine unabhängige Verbindung.
SELECT pg_backend_pid(), current_user;
Der Benutzer ist workshop_write; die Prozess-ID variiert. BEGIN startet eine Transaktion: eine Gruppe von Änderungen, die bei COMMIT gemeinsam sichtbar werden. Bis dahin kann eine andere Verbindung deine neue Buchung nicht sehen. ROLLBACK verwirft noch nicht bestätigte Änderungen. In diesem ersten Beispiel verkauft kein anderer Schalter Plätze für Workshop 101.
RETURNING zeigt die Zeilen, die UPDATE tatsächlich geändert hat. Ziehe 2 Plätze ab und füge die passende Buchung für zwei Plätze in derselben Transaktion ein. Text verwendet einfache Anführungszeichen; der Anfragecode identifiziert diese Buchung eindeutig.
BEGIN;
UPDATE workshops
SET places_left = places_left - 2
WHERE workshop_id = 101
RETURNING workshop_id, places_left;
INSERT INTO bookings (request_code, workshop_id, seats, desk)
VALUES ('normal-group', 101, 2, 'A');
Das Update gibt 101 | 2 zurück. Die Eingabeaufforderung enthält * und kennzeichnet damit eine offene Transaktion. Wechsle zu Workshop Desk und prüfe die per Commit bestätigte Buchungsübersicht:
./desk status
Sie zeigt für Workshop 101 weiterhin 4 Plätze und nur die ursprüngliche Buchung. Wechsle zurück zu Desk A und schließe ab:
COMMIT;
Wechsle zu Workshop Desk:
./desk status
Nun hat 101 noch 2 freie und 3 gebuchte Plätze: den ursprünglichen einen plus deine Zweiergruppe. Ihre tatsächliche Buchung ist normal-group|101|2|A. Eine erfolgreiche Transaktion bewahrt capacity = places_left + booked_seats. Die erste Prüfung liest diese geschäftlichen Fakten; sie leitet nicht ab, wie du sie eingegeben hast.
Vertippst du dich in SQL innerhalb einer Transaktion, kann die Eingabeaufforderung zu ! wechseln. Die Transaktion ist dann abgebrochen: Weiteres SQL schlägt bis zu ROLLBACK; fehl. Rolle in demselben Schalter zurück, prüfe ./desk status in Workshop Desk und wiederhole nur Arbeit, die nicht per Commit bestätigt wurde. Wiederhole niemals eine bereits bestätigte Anfrage blind. Ein Anfragecodekonflikt ist ein Fehler, keine zweite Bestätigung.
Eine Entscheidung auf Grundlage veralteter Verfügbarkeit reproduzieren
In diesem Schritt erzeugst du absichtlich eine ungültige Übungsübersicht, beobachtest, warum ein nicht negativer Zähler unzureichend ist, und begrenzt den Schaden auf Workshop 102.
Wechsle zu Desk A. Dieses Experiment schreibt absichtlich eine veraltete Zahl, statt eine bedingte Platzbeanspruchung zu berechnen. Verwende es nicht als abschließende Buchungsmethode. Die Isolationsstufe READ COMMITTED gibt jeder Anweisung beim Beginn dieser Anweisung eine Sicht auf bestätigte Daten; ein früheres SELECT reserviert sein Ergebnis nicht.
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT workshop_id, places_left FROM workshops WHERE workshop_id = 102;
Wechsle zu Desk B. Bestätige, dass seine Prozess-ID von der von Desk A abweicht, und starte dann seine eigene Transaktion und lies denselben Platz:
SELECT pg_backend_pid(), current_user;
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT workshop_id, places_left FROM workshops WHERE workshop_id = 102;
Beide SELECTs geben einen Platz zurück. Keines hat ihn reserviert. Wechsle zu Desk A und speichere eine Entscheidung anhand dieser früheren Zahl:
UPDATE workshops SET places_left = 0 WHERE workshop_id = 102;
INSERT INTO bookings (request_code, workshop_id, seats, desk)
VALUES ('unsafe-A', 102, 1, 'A');
Lass As Transaktion offen. Wechsle zu Desk B und gib nur dieses UPDATE ein:
UPDATE workshops SET places_left = 0 WHERE workshop_id = 102;
Die Eingabeaufforderung kehrt noch nicht zurück. Dies ist ein erwartetes Warten auf eine Sperre, keine unterbrochene Verbindung. A hat diese Zeile geändert und hält ihre Zeilensperre bis zum Ende der Transaktion. Wechsle zu Workshop Desk:
./desk waits
Das tatsächliche Backend von Desk B hat wait_event_type Lock, und blocked_by enthält As Prozess-ID. Die IDs variieren. Der Beobachter liest nur PostgreSQLs Aktivität; keine Animation und kein Timer erzeugen diesen Zustand.
Wechsle zu Desk A:
COMMIT;
Wechsle zu Desk B. Sein UPDATE wird nun fertig. Es kann den bereits auf null stehenden Bestand weiterhin auf null setzen, weil seine WHERE-Bedingung nur den Workshop benennt. Ergänze seine Bestätigung und bestätige per Commit:
INSERT INTO bookings (request_code, workshop_id, seats, desk)
VALUES ('unsafe-B', 102, 1, 'B');
COMMIT;
Wechsle zu Workshop Desk:
./desk status
Workshop 102 hat nun Kapazität 1, freie Plätze 0 und 2 gebuchte Plätze. Sowohl unsafe-A als auch unsafe-B existieren. Der Bestand erfüllte die Nichtnegativitätsprüfung der Datenbank, doch ein Gast hat keinen Platz. Das Warten allein hat die veraltete geschäftliche Entscheidung nicht korrigiert. Der nächste Schritt entfernt nur diese ausdrücklich entbehrlichen Tests und ersetzt die Entscheidung. Die Schrittprüfung akzeptiert diesen Übungszustand oder den korrekt reparierten Endzustand; sie verwendet den Terminalverlauf nicht als Nachweis deiner Untersuchung.
Den Platz vor seiner Bestätigung beanspruchen
In diesem Schritt verlangst du eine tatsächliche bedingte Platzbeanspruchung, bevor du eine Bestätigung schreibst, und stellst eine abgebrochene wartende Anfrage wieder her.
Wechsle zu Desk A. DELETE entfernt nur Zeilen, die seine WHERE-Bedingung auswählt. IN benennt die beiden entbehrlichen Anfragecodes; lösche weder die ursprüngliche noch die normale Buchung. Stelle in derselben Transaktion nur Übungsworkshop 102 wieder her:
BEGIN;
DELETE FROM bookings WHERE request_code IN ('unsafe-A', 'unsafe-B');
UPDATE workshops SET places_left = 1 WHERE workshop_id = 102;
COMMIT;
Übe zuerst das Abbrechen eines Sperrwartens am separaten Workshop 104. A gibt seine Änderung mit ROLLBACK frei, daher erzeugt dieser Test keine Buchung:
BEGIN;
UPDATE workshops SET places_left = 0 WHERE workshop_id = 104;
Wechsle zu Desk B:
BEGIN;
UPDATE workshops SET places_left = places_left - 1
WHERE workshop_id = 104 AND places_left >= 1
RETURNING workshop_id;
Drücke während Bs Warten Ctrl+C in Desk B. PostgreSQL meldet canceling statement due to user request mit SQLSTATE 57014. SQLSTATE ist PostgreSQLs fünfstellige Fehlerkategorie. Eine abgebrochene Anweisung lässt diese explizite Transaktion im abgebrochenen Zustand. Zeige den Unterschied zu einem gewöhnlichen leeren Ergebnis:
SELECT 1;
Dies ist ein erwarteter Fehler: current transaction is aborted, SQLSTATE 25P02. Stelle den Zustand in Desk B wieder her:
ROLLBACK;
Wechsle zu Desk A und gib seine Übungsänderung frei:
ROLLBACK;
Schütze nun den tatsächlichen letzten Platz in Workshop 102. Die WHERE-Bedingung prüft genügend Plätze als Teil des UPDATE selbst. Nach dem Warten auf eine konkurrierende Aktualisierung prüft READ COMMITTED diese Bedingung erneut gegen die aktualisierte Zeile. RETURNING zeigt dir, ob diese Transaktion den Platz tatsächlich beansprucht hat.
In Desk A:
BEGIN ISOLATION LEVEL READ COMMITTED;
UPDATE workshops SET places_left = places_left - 1
WHERE workshop_id = 102 AND places_left >= 1
RETURNING workshop_id, places_left;
A erhält 102 | 0: Es hat den Platz beansprucht. Erstelle nur deshalb, weil eine Zeile zurückgegeben wurde, seine passende Bestätigung:
INSERT INTO bookings (request_code, workshop_id, seats, desk)
VALUES ('safe-last-A', 102, 1, 'A');
Lass sie ohne Commit. Wechsle zu Desk B:
BEGIN ISOLATION LEVEL READ COMMITTED;
UPDATE workshops SET places_left = places_left - 1
WHERE workshop_id = 102 AND places_left >= 1
RETURNING workshop_id, places_left;
B wartet. Wechsle zu Workshop Desk und lies das tatsächliche Warten:
./desk waits
Eine Zeilensperre sollte unbeteiligte Workshops nicht anhalten. Öffne weiterhin in Workshop Desk eine kurze dritte Verbindung. ./db --desk A verwendet denselben vorbereiteten Datenbanklogin; dieser vorübergehende Shell-Aufruf ist unabhängig vom bereits geöffneten Tab Desk A. Das Here-Dokument in Anführungszeichen sendet alle Zeilen bis zum abschließenden SQL an diesen Client und schließt ihn dann; lass die Endmarkierung allein in ihrer Zeile. Das in der Buchung gespeicherte C kennzeichnet diese dritte Anfrage.
./db --desk A <<'SQL'
BEGIN;
UPDATE workshops SET places_left = places_left - 1
WHERE workshop_id = 103 AND places_left >= 1
RETURNING workshop_id, places_left;
INSERT INTO bookings (request_code, workshop_id, seats, desk)
VALUES ('other-workshop', 103, 1, 'C');
COMMIT;
SQL
Sie gibt 103 | 2 zurück und bestätigt per Commit, während Workshop 102 noch gehalten wird. Wechsle zu Desk A:
COMMIT;
Wechsle zu Desk B. Sein UPDATE gibt nun null Zeilen und UPDATE 0 zurück: Der neue Nullbestand erfüllt places_left >= 1 nicht. Dies ist eine erfolgreiche SQL-Anweisung ohne Platzbeanspruchung, kein SQL-Fehler. Füge keine Bestätigung ein. Beende Bs Transaktion:
ROLLBACK;
Wechsle zu Workshop Desk:
./desk status
Workshop 102 hat eine Buchung, safe-last-A|102|1|A, und null freie Plätze. Workshop 103 hat other-workshop|103|1|C und zwei freie Plätze. Workshop 104 hat weiterhin seinen ursprünglichen einen Platz und keine Buchung; 101 bewahrt seine normale Gruppe und ursprüngliche Familie. Jeder Workshop erfüllt nun die Bilanzgleichung.
Rolle bei einer gewöhnlichen irrtümlichen neuen Testbestätigung zuerst jede offene Transaktion in diesem Schalter mit ROLLBACK zurück. In dieser Übungsumgebung darfst du in einer Transaktion nur deinen eigenen irrtümlichen Code entfernen und nur den Bestand seines Workshops neu berechnen. Hast du zum Beispiel versehentlich typo-trial in Workshop 103 per Commit bestätigt, verwende Folgendes in Desk A und prüfe dann den Status, bevor du die beabsichtigte Anfrage erneut versuchst. Die Unterabfrage summiert tatsächlich gebuchte Plätze; COALESCE(..., 0) verwendet 0, wenn keine Buchungen bleiben. Dies ist ausdrückliche Fehlerbehebung in der Übung, kein Teil des Buchungsalgorithmus:
BEGIN;
DELETE FROM bookings WHERE request_code = 'typo-trial' AND workshop_id = 103;
UPDATE workshops
SET places_left = capacity -
(SELECT COALESCE(SUM(seats), 0) FROM bookings WHERE workshop_id = 103)
WHERE workshop_id = 103;
COMMIT;
Das Beispiel ändert nichts, wenn du diesen Fehler nicht erzeugt hast. Verwende es niemals, um eine ursprüngliche oder berechtigte angeforderte Buchung zu löschen. Das nächste Lab verknüpft Platzbeanspruchung und Bestätigung in einer wiederverwendbaren Anweisung, sodass der Aufrufer nach einer leeren Platzbeanspruchung nicht versehentlich einfügen kann.
Zusammenfassung
Du hast eine tatsächliche normale Buchung erstellt, eine Überbuchung durch veraltetes Lesen reproduziert und den letzten Platz mit einem bedingten UPDATE und einer Bestätigung in derselben Transaktion geschützt. Native Sperrbeobachtungen zeigten einen wartenden konkurrierenden Schalter, während ein anderer Workshop weiterlief. Du hast außerdem eine wartende Anweisung abgebrochen und ihre abgebrochene Transaktion mit ROLLBACK wiederhergestellt. Deine abschließende Buchungsübersicht bewahrt ursprüngliche Buchungen und ordnet jeden bestätigten Platz der verbleibenden Kapazität zu.



