Поскольку вы не говорите нам многое из того, что вам нужно, я догадываюсь обо всем, и мы сделаем это довольно сложным, чтобы упростить некоторые из возможных вопросов.
Первое, что касается MVCC, это то, что в системе с высокой степенью параллелизма вы хотите избежать блокировки таблиц. Как правило, вы не можете сказать, что не существует, не блокируя таблицу для транзакции. Это оставляет вам один вариант: не полагайтесь на INSERT
.
Я оставляю очень мало в качестве упражнения для реального приложения бронирования здесь. Мы не справляемся,
- Перебронирование (как функция)
- Или что делать, если не осталось х-мест.
- Сборка для клиента и транзакции.
Ключ здесь в том, что UPDATE.
мы блокируем только строки UPDATE
перед началом транзакции. Мы можем сделать это, потому что мы вставили все билеты на места для продажи в таблицу event_venue_seats
.
Создать базовую схему
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)
);
Тестовые данные
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);
А теперь для транзакции бронирования
Теперь у нас есть четкая и жестко запрограммированная единица, вы должны установить это на любое событие, которое вы хотите, customerid
и, по txnid
сути, зарезервировать место и сказать, кто это сделал. Это FOR UPDATE
ключ. Эти строки заблокированы во время обновления.
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;
Обновления
Для временных бронирований
Вы бы использовали временное бронирование. Например, когда вы покупаете билеты на концерт, у вас есть M минут для подтверждения бронирования, или у кого-то еще есть шанс - Нил МакГиган 19 минут назад
Что бы вы сделали здесь, это установить booking.event_venue_seats.txnid
как
txnid int REFERENCES transactions ON DELETE SET NULL
Во-вторых, пользователь резервирует сет, UPDATE
ставит в txnid. Ваша таблица транзакций выглядит примерно так.
CREATE TABLE transactions (
txnid serial PRIMARY KEY,
txn_start timestamp DEFAULT now(),
txn_expire timestamp DEFAULT now() + '5 minutes'
);
Тогда в каждую минуту ты бежишь
DELETE FROM transactions
WHERE txn_expire < now()
Вы можете предложить пользователю продлить таймер при приближении срока действия. Или просто позвольте ему удалить txnid
и каскадом освободить места.
Я думаю, что это может быть достигнуто с помощью небольшого двойного стола и некоторых ограничений.
Давайте начнем с некоторой (не полностью нормализованной) структуры:
В таблице заказов вместо
is_booked
столбца естьbooker
столбец. Если значение равно нулю, место не забронировано, в противном случае это имя (идентификатор) участника.Мы добавим несколько примеров данных ...
Мы создаем вторую таблицу для бронирований с одним ограничением:
Эта вторая таблица будет содержать КОПИЮ кортежей (session_id, seat_number, booker) с одним
FOREIGN KEY
ограничением; это не позволит обновлять первоначальные заказы другой задачей. [Предполагая, что никогда не бывает двух задач, связанных с одним и тем же букером ; если это так, тоtask_id
следует добавить определенный столбец.]Всякий раз, когда нам нужно сделать бронирование, последовательность шагов, выполняемых в рамках следующей функции, показывает путь:
Чтобы действительно сделать заказ, ваша программа должна попытаться выполнить что-то вроде:
Это основывается на двух фактах: 1.
FOREIGN KEY
Ограничение не позволяет нарушать данные . 2. Мы ОБНОВЛЯЕМ таблицу бронирований, но только INSERT (и никогда не ОБНОВЛЯЕМ ) на bookings_with_bookers one (вторая таблица).Ему не нужен
SERIALIZABLE
уровень изоляции, что значительно упростит логику. На практике, однако, следует ожидать взаимоблокировок , и программа, взаимодействующая с базой данных, должна быть рассчитана на их обработку.источник
SERIALIZABLE
потому что, если две book_sessions выполняются одновременно,count(*)
вторая txn может прочитать таблицу до того, как первая book_session завершит работу с нейINSERT
. Как правило, проверять несуществование wo / небезопасноSERIALIZABLE
.Я бы использовал
CHECK
ограничение, чтобы предотвратить перебронирование и избежать явной блокировки строк.Таблица может быть определена так:
Бронирование партии мест осуществляется одним
UPDATE
:Ваш код должен иметь логику повторных попыток. Обычно просто попробуйте запустить это
UPDATE
. Сделка будет состоять из этогоUPDATE
. Если проблем не было, можете быть уверены, что вся партия была забронирована. Если вы получили нарушение ограничения CHECK, повторите попытку.Итак, это оптимистичный подход.
UPDATE
, потому что ограничение (то есть механизм БД) делает это за вас.источник
1 подход - одиночное ОБНОВЛЕНИЕ:
2-й подход - LOOP (plpgsql):
3-й подход - таблица очередей:
Сами транзакции не обновляют таблицу мест. Все они ВСТАВЛЯЮТ свои запросы в таблицу очередей. Отдельный процесс принимает все запросы из таблицы очереди и обрабатывает их, путем выделения места на реквестеры.
Преимущества:
- с помощью INSERT устраняется блокировка / конфликт
- нет перебронирования за счет использования единого процесса для распределения мест
Недостатки:
- Распределение мест не сразу
источник