Здравствуйте.
Есть задача - рассадить (зарегистрировать и назначить места в зале) приходящих на мероприятие около 1000 людей без коллизий. Регистрация будет проходить не нескольких компьютерах одновременно, и регистрируемый имеет право попросить посадить себя рядом с несколькими другими людьми. Выбор, куда именно человека посадить - не предоставляется.
Возможные места для рассадки содержатся в таблице place (id, sector, row, col, is_busy, is_closed).
Список людей к посадке (точнее, их билетов) - содержится во временной таблице, заполняемой вместе с регистрацией нового или существующего билета. Также, имеет место предварительная регистрация, т.е. билеты с уже заполненными местами.
После создания или регистрации существующих билетов и их объединения для посадки рядом (добавления записей в gtt_tickets_to_land) буду вызывать процедуру рассадки и после ее выполнения незамедлительно делать commit.
Процедуру рассадки написал, отладил, теперь прошу комментариев сообщества о том, что сделано неправильно.
База - firebird 2.5.1, параметры пишущей транзакции - write,nowait,rec_version,read_committed.
Таблицы:
place (event - мероприятие, sector - сектор, row - ряд, col - место в ряду) - все существующие места в зале. предполагается, что дырок в местах одного ряда нет.
ticket (event - мероприятие, employee - человек, place - место, ссылка на place.id) - билеты на мероприятие
gtt_tickets_to_land (ticket - ссылка на ticket.id) - билеты, собранные для посадки вместе
create or alter procedure CRB_LAND_TICKETS
AS
declare variable l_event id_domain;
declare variable l_tickets_count integer;
declare variable l_sector id_domain = 0;
declare variable l_done integer = 0;
declare variable l_row integer = 0;
declare variable l_col integer = 0;
declare variable l_loop_counter integer = 0;
begin
-- сколько билетов надо посадить
begin
select t.event, count(*)
from gtt_tickets_to_land tl
join ticket t on t.id = tl.ticket
group by t.event
into :l_event, :l_tickets_count;
when
-- если в разных мероприятиях
gdscode sing_select_err do
exception e_tickets_different_event;
end
/*
- в цикле ищем подходящие записи и пытаемся их записать
- если ошибка блокировки или записано меньше чем нужно записей то идем на след. итерацию
*/
while (l_done = 0 and l_loop_counter < 1000) do
begin
-- ищем подходящие места (l_tickets_count подряд идущих)
select first 1 p.sector, p.row, p.col
from place p
-- пропускаем закрытые сектора
join sector s on s.id = p.sector and s.is_closed = 0
where
p.event = :l_event and
p.is_busy = 0 and
p.is_closed = 0 and
-- после заблокированных, т.е. не подошедших в прошлый ра
(
p.sector > :l_sector or
(p.sector = :l_sector and p.row > :l_row) or
(p.sector = :l_sector and p.row = :l_row and p.col > :l_col) or
1=0
) and
(
select count(*)
from place p2
where
p2.event = :l_event and
p2.sector = p.sector and
p2.row = p.row and
p2.col between p.col and p.col + :l_tickets_count - 1 and
p2.is_closed = 0 and
p2.is_busy = 0 and
1=1
) = :l_tickets_count and
1=1
order by p.sector, p.row, p.col
into :l_sector, :l_row, :l_col;
-- пытаемся взять их себе
begin
update place p
set p.is_busy = 1
where
p.event = :l_event and
p.sector = :l_sector and
p.is_busy = 0 and
p.is_closed = 0 and
p.row = :l_row and
p.col between :l_col and :l_col + :l_tickets_count - 1 and
1=1
;
-- если не были заняты после пред. select-а
if (row_count = :l_tickets_count) then
l_done = 1;
when
-- если заняты по блокировке, ищем дальше
gdscode lock_conflict,
gdscode lock_timeout,
gdscode deadlock
do
begin
end
end
l_loop_counter = l_loop_counter + 1;
end
-- сажаем на выбранные места
if (l_done = 1) then
merge into ticket t
using (
select tl.ticket, p.id place
from gtt_tickets_to_land tl
join place p on
p.event = :l_event and
p.sector = :l_sector and
p.row = :l_row and
p.col between :l_col and :l_col + :l_tickets_count - 1 and
1=1
) v
on (v.ticket = t.id)
when matched then
update set
t.place = v.place
;
else
exception e_cannot_land_tickets;
end