Como você não está nos dizendo muito do que precisa, vou adivinhar tudo e tornaremos moderadamente complexo simplificar algumas das perguntas possíveis.
A primeira coisa sobre o MVCC é que, em um sistema altamente concorrente, você deseja evitar o bloqueio de tabelas. Como regra geral, você não pode dizer o que não existe sem bloquear a tabela para a transação. Isso deixa uma opção: não confie INSERT
.
Deixo muito pouco como exercício para um aplicativo de reservas real aqui. Nós não lidamos,
- Overbooking (como um recurso)
- Ou o que fazer se não houver x lugares restantes.
- Desenvolvimento para cliente e transação.
A chave aqui está em UPDATE.
Nós bloqueamos apenas as linhas UPDATE
antes do início da transação. Podemos fazer isso porque inserimos todos os ingressos para venda na tabela event_venue_seats
,.
Crie um esquema básico
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)
);
Dados de teste
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);
E agora para a transação de reserva
Agora temos a eventid codificado para um, você deve definir isso para qualquer evento que você quer, customerid
e txnid
essencialmente fazer o assento reservado e dizer-lhe quem fez isso. A FOR UPDATE
chave é. Essas linhas são bloqueadas durante a atualização.
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;
Atualizações
Para reservas programadas
Você usaria uma reserva programada. Como quando você compra ingressos para um show, você tem M minutos para confirmar a reserva ou outra pessoa tem a chance - Neil McGuigan 19 mins atrás
O que você faria aqui é definir booking.event_venue_seats.txnid
como
txnid int REFERENCES transactions ON DELETE SET NULL
O segundo que o usuário reserva o seet, o UPDATE
coloca no txnid. Sua tabela de transações se parece com isso.
CREATE TABLE transactions (
txnid serial PRIMARY KEY,
txn_start timestamp DEFAULT now(),
txn_expire timestamp DEFAULT now() + '5 minutes'
);
Então, a cada minuto você corre
DELETE FROM transactions
WHERE txn_expire < now()
Você pode solicitar ao usuário que estenda o cronômetro quando estiver próximo do vencimento. Ou, deixe-o excluir txnid
e desça em cascata, liberando os assentos.
Acho que isso pode ser conseguido com o uso de uma pequena mesa dupla sofisticada e algumas restrições.
Vamos começar por alguma estrutura (não totalmente normalizada):
As reservas da tabela, em vez de ter uma
is_booked
coluna, possuem umabooker
coluna. Se for nulo, o assento não será reservado, caso contrário, esse é o nome (id) do contratante.Nós adicionamos alguns dados de exemplo ...
Criamos uma segunda tabela para reservas, com uma restrição:
Esta segunda tabela conterá uma CÓPIA das tuplas (session_id, seat_number, booker), com uma
FOREIGN KEY
restrição; isso não permitirá que as reservas originais sejam ATUALIZADAS por outra tarefa. [Supondo que nunca haja duas tarefas lidando com o mesmo agendador ; se for esse o caso, uma determinadatask_id
coluna deve ser adicionada.]Sempre que precisamos fazer uma reserva, a sequência de etapas seguidas na seguinte função mostra o caminho:
Para realmente fazer uma reserva, seu programa deve tentar executar algo como:
Isso depende de dois fatos: 1. A
FOREIGN KEY
restrição não permitirá que os dados sejam quebrados . 2. ATUALIZAMOS a tabela de reservas, mas apenas INSERT (e nunca UPDATE ) no bookings_with_bookers um (a segunda tabela).Não precisa de
SERIALIZABLE
nível de isolamento, o que simplificaria bastante a lógica. Na prática, no entanto, são esperados impasses , e o programa que interage com o banco de dados deve ser projetado para lidar com eles.fonte
SERIALIZABLE
porque, se duas sessões de livro forem executadas ao mesmo tempo, acount(*)
partir do segundo txn poderá ler a tabela antes que a primeira sessão de livro seja concluídaINSERT
. Como regra geral, não é seguro testar a inexistência de wo /SERIALIZABLE
.Eu usaria uma
CHECK
restrição para evitar reservas em excesso e evitar o bloqueio explícito de linhas.A tabela pode ser definida assim:
A reserva de um lote de assentos é feita por um único
UPDATE
:Seu código deve ter uma lógica de nova tentativa. Normalmente, simplesmente tente executar isso
UPDATE
. A transação consistiria nessaUPDATE
. Se não houver problemas, você pode ter certeza de que todo o lote foi contratado. Se você receber uma violação da restrição CHECK, tente novamente.Portanto, essa é uma abordagem otimista.
UPDATE
, porque a restrição (ou seja, o mecanismo do banco de dados) faz isso por você.fonte
Abordagem 1s - atualização única:
2a abordagem - LOOP (plpgsql):
3ª abordagem - tabela de filas:
As transações em si não atualizam a tabela de assentos. Todos eles inserem seus pedidos em uma tabela de filas.
Um processo separado pega todas as solicitações da tabela de filas e as manipula, alocando assentos aos solicitantes.
Vantagens:
- Ao usar INSERT, o bloqueio / contenção é eliminado
- Nenhuma reserva em excesso é garantida usando um único processo para alocação de assentos
Desvantagens:
- A alocação de assentos não é imediata
fonte