Parce que vous ne nous dites pas grand-chose de ce dont vous avez besoin, je devine pour tout, et nous allons le rendre modérément complexe pour simplifier certaines des questions possibles.
La première chose à propos de MVCC est que dans un système hautement simultané, vous voulez éviter le verrouillage de table. En règle générale, vous ne pouvez pas dire ce qui n'existe pas sans verrouiller la table de la transaction. Cela vous laisse une option: ne comptez pas sur INSERT
.
Je laisse très peu comme exercice pour une vraie application de réservation ici. Nous ne gérons pas,
- Overbooking (en tant que fonctionnalité)
- Ou que faire s'il n'y a pas de sièges x restants.
- Buildout au client et transaction.
La clé ici se trouve dans la zone UPDATE.
Nous verrouillons uniquement les lignes UPDATE
avant le début de la transaction. Nous pouvons le faire parce que nous avons tous insérés sièges-billets à vendre dans le tableau, event_venue_seats
.
Créer un schéma de base
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)
);
Données de test
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);
Et maintenant pour la transaction de réservation
Maintenant, nous avons le code événementiel en dur, vous devez le régler sur l'événement que vous voulez, customerid
et txnid
essentiellement réserver le siège et vous dire qui l'a fait. C'est la FOR UPDATE
clé. Ces lignes sont verrouillées lors de la mise à jour.
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;
Mises à jour
Pour les réservations chronométrées
Vous utiliseriez une réservation chronométrée. Comme lorsque vous achetez des billets pour un concert, vous avez M minutes pour confirmer la réservation, ou quelqu'un d'autre a la chance - Neil McGuigan Il y a 19 minutes
Qu'est - ce que vous feriez ici est fixé le booking.event_venue_seats.txnid
comme
txnid int REFERENCES transactions ON DELETE SET NULL
La seconde où l'utilisateur réserve le seet, le UPDATE
met dans le txnid. Votre table de transactions ressemble à ceci.
CREATE TABLE transactions (
txnid serial PRIMARY KEY,
txn_start timestamp DEFAULT now(),
txn_expire timestamp DEFAULT now() + '5 minutes'
);
Puis à chaque minute où tu cours
DELETE FROM transactions
WHERE txn_expire < now()
Vous pouvez inviter l'utilisateur à prolonger la minuterie à l'approche de l'expiration. Ou, laissez-le simplement supprimer le txnid
et descendez en cascade pour libérer les sièges.
Je pense que cela peut être accompli en utilisant une petite table double fantaisie et quelques contraintes.
Commençons par une structure (pas complètement normalisée):
Les réservations de table, au lieu d'avoir une
is_booked
colonne, ont unebooker
colonne. S'il est nul, le siège n'est pas réservé, sinon c'est le nom (id) du booker.Nous ajoutons quelques exemples de données ...
Nous créons une deuxième table pour les réservations, avec une restriction:
Cette deuxième table contiendra une COPIE des tuples (session_id, seat_number, booker), avec une
FOREIGN KEY
contrainte; cela ne permettra pas de mettre à jour les réservations d'origine par une autre tâche. [En supposant qu'il n'y a jamais deux tâches concernant le même booker ; si tel était le cas, une certainetask_id
colonne devrait être ajoutée.]Chaque fois que nous devons faire une réservation, la séquence d'étapes suivie dans la fonction suivante montre le chemin:
Pour vraiment faire une réservation, votre programme doit essayer d'exécuter quelque chose comme:
Cela repose sur deux faits 1. La
FOREIGN KEY
contrainte ne permettra pas de casser les données . 2. Nous METTONS À JOUR la table des réservations, mais INSÉRONS seulement (et jamais METTRE À JOUR ) sur la table bookings_with_bookers (la deuxième table).Il n'a pas besoin de
SERIALIZABLE
niveau d'isolement, ce qui simplifierait considérablement la logique. En pratique, cependant, des blocages sont à prévoir et le programme interagissant avec la base de données doit être conçu pour les gérer.la source
SERIALIZABLE
car si deux book_sessions sont exécutées en même temps,count(*)
le deuxième txn pourrait lire la table avant que la première book_session n'en ait fini avec sonINSERT
. En règle générale, il n'est pas sûr de tester la non-existence de wo /SERIALIZABLE
.J'utiliserais une
CHECK
contrainte pour empêcher la surréservation et éviter le verrouillage explicite des lignes.La table pourrait être définie comme ceci:
La réservation d'un lot de places se fait par un seul
UPDATE
:Votre code doit avoir une logique de nouvelle tentative. Normalement, essayez simplement d'exécuter ceci
UPDATE
. La transaction comprendrait celle-ciUPDATE
. S'il n'y a eu aucun problème, vous pouvez être sûr que le lot entier a été réservé. Si vous obtenez une violation de contrainte CHECK, vous devez réessayer.Il s'agit donc d'une approche optimiste.
UPDATE
, car la contrainte (c'est-à-dire le moteur de base de données) le fait pour vous.la source
Approche 1s - MISE À JOUR unique:
2ème approche - LOOP (plpgsql):
3ème approche - Table de file d'attente:
Les transactions elles - mêmes ne mettent pas à jour la table des sièges. Ils INSÉRENT tous leurs demandes dans une table de file d'attente.
Un processus distinct prend toutes les demandes de la table de file d'attente et les gère, en allouant des sièges aux demandeurs.
Avantages:
- En utilisant INSERT, le verrouillage / conflit est éliminé
- Aucune surréservation n'est assurée en utilisant un processus unique pour l'attribution des sièges
Inconvénients:
- L'attribution des sièges n'est pas immédiate
la source