Da Sie uns nicht viel von dem erzählen, was Sie brauchen, werde ich für alles raten, und wir werden es mäßig komplex machen, einige der möglichen Fragen zu vereinfachen.
Das erste an MVCC ist, dass Sie in einem System mit hoher Gleichzeitigkeit das Sperren von Tabellen vermeiden möchten. In der Regel können Sie nicht feststellen, was nicht vorhanden ist, ohne die Tabelle für die Transaktion zu sperren. Damit haben Sie eine Möglichkeit: Verlassen Sie sich nicht darauf INSERT
.
Ich lasse hier sehr wenig als Übung für eine echte Buchungs-App. Wir kümmern uns nicht,
- Überbuchung (als Feature)
- Oder was tun, wenn keine X-Plätze mehr vorhanden sind?
- Buildout zum Kunden und zur Transaktion.
Der Schlüssel hier ist in UPDATE.
Wir sperren nur die Zeilen für, UPDATE
bevor die Transaktion beginnt. Wir können dies tun, weil wir alle zum Verkauf stehenden Sitzplatzkarten in die Tabelle eingefügt haben event_venue_seats
.
Erstellen Sie ein Basisschema
CREATE SCHEMA booking;
CREATE TABLE booking.venue (
venueid serial PRIMARY KEY,
venue_name text NOT NULL
-- stuff
);
CREATE TABLE booking.seats (
seatid serial PRIMARY KEY,
venueid int REFERENCES booking.venue,
seatnum int,
special_notes text,
UNIQUE (venueid, seatnum)
--stuff
);
CREATE TABLE booking.event (
eventid serial PRIMARY KEY,
event_name text,
event_timestamp timestamp NOT NULL
--stuff
);
CREATE TABLE booking.event_venue_seats (
eventid int REFERENCES booking.event,
seatid int REFERENCES booking.seats,
txnid int,
customerid int,
PRIMARY KEY (eventid, seatid)
);
Testdaten
INSERT INTO booking.venue (venue_name)
VALUES ('Madison Square Garden');
INSERT INTO booking.seats (venueid, seatnum)
SELECT venueid, s
FROM booking.venue
CROSS JOIN generate_series(1,42) AS s;
INSERT INTO booking.event (event_name, event_timestamp)
VALUES ('Evan Birthday Bash', now());
-- INSERT all the possible seat permutations for the first event
INSERT INTO booking.event_venue_seats (eventid,seatid)
SELECT eventid, seatid
FROM booking.seats
INNER JOIN booking.venue
USING (venueid)
INNER JOIN booking.event
ON (eventid = 1);
Und jetzt zur Buchungstransaktion
Jetzt haben wir die Ereignis-ID fest auf eins codiert. Sie sollten dies auf ein beliebiges Ereignis einstellen customerid
und im txnid
Wesentlichen den Sitzplatz reservieren und Ihnen mitteilen, wer es getan hat. Das FOR UPDATE
ist der Schlüssel. Diese Zeilen werden während des Updates gesperrt.
UPDATE booking.event_venue_seats
SET customerid = 1,
txnid = 1
FROM (
SELECT eventid, seatid
FROM booking.event_venue_seats
JOIN booking.seats
USING (seatid)
INNER JOIN booking.venue
USING (venueid)
INNER JOIN booking.event
USING (eventid)
WHERE txnid IS NULL
AND customerid IS NULL
-- for which event
AND eventid = 1
OFFSET 0 ROWS
-- how many seats do you want? (they're all locked)
FETCH NEXT 7 ROWS ONLY
FOR UPDATE
) AS t
WHERE
event_venue_seats.seatid = t.seatid
AND event_venue_seats.eventid = t.eventid;
Aktualisierung
Für zeitgesteuerte Reservierungen
Sie würden eine zeitgesteuerte Reservierung verwenden. Wenn Sie beispielsweise Tickets für ein Konzert kaufen, haben Sie M Minuten Zeit, um die Buchung zu bestätigen, oder jemand anderes hat die Chance - Neil McGuigan vor 19 Minuten
Was Sie hier tun würden, ist das Einstellen booking.event_venue_seats.txnid
von
txnid int REFERENCES transactions ON DELETE SET NULL
In der Sekunde, in der der Benutzer das Seet reserviert, UPDATE
setzt er das txnid ein. Ihre Transaktionstabelle sieht ungefähr so aus.
CREATE TABLE transactions (
txnid serial PRIMARY KEY,
txn_start timestamp DEFAULT now(),
txn_expire timestamp DEFAULT now() + '5 minutes'
);
Dann rennst du in jeder Minute
DELETE FROM transactions
WHERE txn_expire < now()
Sie können den Benutzer auffordern, den Timer zu verlängern, wenn er sich dem Ablauf nähert. Oder lassen Sie es einfach löschen txnid
und kaskadieren Sie die Sitze frei.
Ich denke, dies kann durch die Verwendung eines kleinen ausgefallenen Doppeltisches und einiger Einschränkungen erreicht werden.
Beginnen wir mit einer (nicht vollständig normalisierten) Struktur:
Die Tabellenbuchungen haben anstelle einer
is_booked
Spalte einebooker
Spalte. Wenn es null ist, ist der Sitzplatz nicht gebucht, andernfalls ist dies der Name (ID) des Buchers.Wir fügen einige Beispieldaten hinzu ...
Wir erstellen eine zweite Tabelle für Buchungen mit einer Einschränkung:
Diese zweite Tabelle enthält eine KOPIE der Tupel (session_id, seat_number, booker) mit einer
FOREIGN KEY
Einschränkung. das wird nicht die Original - Buchungen ermöglicht durch eine andere Aufgabe zu aktualisierenden. [Angenommen, es gibt nie zwei Aufgaben, die sich mit demselben Bucher befassen . In diesem Fall sollte eine bestimmtetask_id
Spalte hinzugefügt werden.]Wann immer wir eine Buchung vornehmen müssen, zeigt die Abfolge der Schritte, die in der folgenden Funktion ausgeführt werden, den Weg:
Um wirklich eine Buchung vorzunehmen, sollte Ihr Programm versuchen, Folgendes auszuführen:
Dies beruht auf zwei Tatsachen: 1. Die
FOREIGN KEY
Einschränkung lässt nicht zu, dass die Daten beschädigt werden . 2. Wir AKTUALISIEREN die Buchungstabelle, aber nur INSERT (und niemals UPDATE ) für die bookings_with_bookers one (die zweite Tabelle).Es wird keine
SERIALIZABLE
Isolationsstufe benötigt , was die Logik erheblich vereinfachen würde. In der Praxis sind jedoch Deadlocks zu erwarten, und das mit der Datenbank interagierende Programm sollte so ausgelegt sein, dass sie diese verarbeiten.quelle
SERIALIZABLE
weil, wenn zwei book_sessions gleichzeitig ausgeführt werden, dercount(*)
vom zweiten txn die Tabelle lesen könnte, bevor die erste book_session mit seinem fertig istINSERT
. In der Regel ist es nicht sicher, auf Nichtexistenz zu testenSERIALIZABLE
.Ich würde eine
CHECK
Einschränkung verwenden, um eine Überbuchung und ein explizites Sperren von Zeilen zu verhindern.Die Tabelle könnte folgendermaßen definiert werden:
Die Buchung einer Reihe von Sitzplätzen erfolgt durch eine Einzelperson
UPDATE
:Ihr Code sollte eine Wiederholungslogik haben. Versuchen Sie normalerweise einfach, dies auszuführen
UPDATE
. Die Transaktion würde aus dieser bestehenUPDATE
. Wenn es keine Probleme gab, können Sie sicher sein, dass die gesamte Charge gebucht wurde. Wenn Sie eine CHECK-Einschränkungsverletzung erhalten, sollten Sie es erneut versuchen.Dies ist also ein optimistischer Ansatz.
UPDATE
, da die Einschränkung (dh die DB-Engine) dies für Sie erledigt.quelle
1s Ansatz - Single UPDATE:
2. Ansatz - LOOP (plpgsql):
3. Ansatz - Warteschlangentabelle:
Die Transaktionen selbst aktualisieren den Sitztisch nicht. Sie alle fügen ihre Anforderungen in eine Warteschlangentabelle ein.
Ein separater Prozess nimmt alle Anforderungen aus der Warteschlangentabelle und verarbeitet sie, indem den Anforderern Sitzplätze zugewiesen werden.
Vorteile:
- Durch die Verwendung von INSERT werden Sperren / Konflikte beseitigt.
- Durch die Verwendung eines einzigen Prozesses für die Sitzplatzzuweisung wird keine Überbuchung sichergestellt
Nachteile:
- Die Sitzplatzzuweisung erfolgt nicht sofort
quelle