|
|
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Здравствуйте, форумчане. Замучался уже перекапывать интернет, решился, наконец, задать вопрос знатокам. Нужно спроекторовать БД для системы бронирования номеров в гостинице. Задача вроде бы несложная, но есть одно НО: бронируемые единицы имеют иерархичесткую структуру. К примеру, есть двухэтажный домик, на каждом этаже по две комнаты. Забронировать можно как домик целиком, так и в отдельности каждый этаж и каждую комнату. Имееющиеся таблицы: vacancies ( vacancy_id, parent_vacancy_id, vacancy_name ); booking_orders ( booking_order_id, date_start, date_end ); bookings ( booking_order_id, vacancy_id ); Очевидно, что если забронировать комнату на втором этаже в период с 5 мая по 10 мая, то весь второй этаж, а также домик целиком забронировать на этот период уже не получится. Также очевидно и обратное, что если на этот период забронировать домик целиком - то нельзя будет забронировать ни одну из комнат и ни один из этажей этого домика. Вопрос: каким образом реализовать эти запреты в БД? Я использую PostgreSQL, и без иерархической структуры это можно сделать простым применением CHECK. Но так как в моем случае проверяться должны ещё потомки и предки - я зашел в тупик. Точнее, я загнал в тупик БД, создав такой триггер, который использует блокироровки и при нескольких конкурентных запросах создает deadlock :)) (хотя, в документации к PostgreSQL написано, что СУБД умеет справляться с дедлоками, убивая одину из конкурирующих транзакций) Если блокировки не использовать - то возникают ситуации, когда бронирования накладываются друг на друга. Я знаю, почему это происходит, но не могу понять, как от этого избавиться. Мой триггер имеет вид after insert on bookings for each row execute procedure ... Если две конкурентных транзакции одновременно пытаются записать в таблицу bookings противоречащие данные, то для обеих вызывается этот триггер, но так как обе транзакции еще не завершены, то при проверке противоречий при чтении из таблицы bookings одна транзакция не видит результаты работы другой, и поэтому обе завершаются успешно. В результате мы имеем 2 записи, которые "проскочили" проверку валидности, но противоречат друг другу, так как их периоды бронирования пересекаются. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 03:56 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Кстати, для уточнения: если транзакции выполняются последовательно, то триггер работает, и попытки создать запись, противоречащюю уже имеющейся, вызывают ошибку. То есть, дело именно в конкурентных одновременных транзакциях. Я создал тестовое мультипоточное приложение (100 потоков, использующие каждый отдельное соединение с БД, одновременно создают случайные записи в таблице бронирования в пределах установленных ограничений), которое и выявило проблему. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 04:15 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Количество иерархий конечно? Домик-этаж-комната? То бишь угол в комнате внезапно не появится, а если появится то пользователь готов оплатить переделку? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:00 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Количество иерархий неограничено. Система должна быть как можно более универсальной. Может случиться и такое, что заказчик захочет каждую койку в комнате по-отдельности бронировать, это должно быть предусмотрено. Я думаю, единственным условием сделать то, чтобы заказчик четко вбил иерархическую структуру, и больше не менял её во время использования. При желании её как-то расширить ему придется создавать новую структуру. То есть, был домик с двумя комнатами, если он хочет сдавать там койки - то пусть создаст в БД новый домик с двумя комнатами и койками, а не добавляет койки в уже готовую структуру. Хотя, если можно найти простое и элегантное решение, чтобы гибко изменять структуру во время эксплуатации без побочных эффектов - было бы совсем хорошо. И волки сыты, и овцы целы :) Но мне кажется, это будет очень сложно, придется отслеживать очень много зависимостей. Хотя, вот родилась идея использовать массивы для хранения ID всех зависимых единиц. Может, что-то из этого получится. И гибкое, и надёжное :) Пятый час утра - а я тут сижу реализовываю, чтоб не забыть как проснусь )) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:15 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Единица бронирования день? Если так то можно создать таблицу Код: sql 1. 2. и избавится от проверочного триггера как класса (приложение вставит по записи на каждую комнату каждого дня при бронировании на верхнем уровне) а ошибку будет генерить нарушение первичного ключа ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:18 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Ещё сразу уточню, что в БД могут быть иерархии с разными степенями вложения. То есть, может быть просто однокомнатный домик (то есть, просто либо домик, либо комната, без зависимостей), может быть двухкомнатный домик, может быть двухэтажный домик с двумя комнатами на первом этаже, тремя на втором, и еще чердаком. То есть, все зависит от фантазии заказчика :) И всё это одновременно должно находится в БД, не конфликтовать друг с другом и правильным образом отслеживаться при бронировании. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:19 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
SERG1257Единица бронирования день? Если так то можно создать таблицу Код: sql 1. 2. и избавится от проверочного триггера как класса (приложение вставит по записи на каждую комнату каждого дня при бронировании на верхнем уровне) а ошибку будет генерить нарушение первичного ключа Единица бронирования - произвольный период, измеряемый в днях. То есть, с 5 по 15 мая. Или с 3 июня по 6 сентября. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:20 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Если дни, то количество строк будет не слишком большое (часы или минуты были бы гораздо хуже) надо бы добавить в таблицу booking поле book_id со ссылкой на бронирование, чтобы не только добавлять строки, но и удалять (снимать бронирование) Про иерархии http://www.osp.ru/pcworld/2007/03/4199032/ Выбирайте любой способ ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:49 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Так же надо будет предусмотреть механизм удаления (архивации) исторических записей (скажем старше трех месяцев) из рабочей таблицы ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 06:51 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
SERG1257, За ссылочку большое спасибо, нашел там ответ на один нужный вопрос. А насчет архивации - это да, уже думал об этом. Кроме архивации там ещё много интересных фишек можно сделать, чтоб снизить нагрузку на сервер. Но оптимизацией производительности я буду заниматься позже, сейчас бы заставить эту махину работать :) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 07:38 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Ещё одна вещь, которую я не понимаю: у меня есть триггер вида: create constraint trigger trig_checkBookingPeriod after insert on bookings FROM booking_orders for each row execute procedure checkBookingPeriod(); если в него добавить строку LOCK TABLE bookings IN SHARE MODE; То возникает deadlock при попытке вставить строку в таблицу несколькими параллельными потоками. Если поток один - то все работает нормально. Откуда берется дэдлок? В самой процедуре триггера никаких записей ни в какие таблицы не делается, только чтение для проверки консистенции. Есть догадка, что он может возникать в случае, если проверка в треггере не проходит и выстреливает исключение (exception), что приводит к откату транзакции. Тогда системой из таблицы удаляется запись, которая не прошла проверку валидности, для этого делается LOCK IN EXCLUSIVE MODE, а это уже может вызвать дэдлок. В общем, понятно, что ничего не понятно =/ ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 10:38 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Uniquad Ещё одна вещь, которую я не понимаю: у меня есть триггер вида:Это в соседний форум . Uniquad Я думаю, единственным условием сделать то, чтобы заказчик четко вбил иерархическую структуру, и больше не менял её во время использования. При желании её как-то расширить ему придется создавать новую структуру. То есть, был домик с двумя комнатами, если он хочет сдавать там койки - то пусть создаст в БД новый домик с двумя комнатами и койками, а не добавляет койки в уже готовую структуруПопробуйте договорится с заказчиком и ограничить число уровней иерархии. Пусть будут не три, а пять или скажем десять. Если при этом детали типа маленький домик имеет один этаж, одну комнату, один угол, одну кровать, одну подушку будут скрыты от пользователя интерфейсом, то у пользователя может сложится впечатление, что количество иерархий неограничено (в пределах установленного максимума). Я могу сделать автомобиль любого цвета, если этот цвет черный (с) Генри Форд. Иначе вам придется парсить каждое бронирование на лету, приводя его к общему знаменателю. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 18:35 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
UniquadЕщё сразу уточню, что в БД могут быть иерархии с разными степенями вложения. Да не суть, имхо. В любом случае вопрос довольно прост, имхо: при создании блокировки X следует создать также каскадные блокировки X1, X2, X3 на "родителя", "дедушку" и прочее вплоть до корневого объекта. Да и с диапазонами вряд ли стоит возиться, можно размножать записи по дням периода. Обеспечивать корректность блокировки, соответственно, будет простой unique. Ну а количество записей в таблице блокировок.. зависит от объекта, конечно, но на глаз будет никак не больше ста тысяч. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 18:59 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
softwarer, Каскадные блокировки не рулят. Если у родителя есть два потомка, и один потомок каскадом заблокирует родителя, то второй останется незаблокированным, но заблокировать его уже будет нельзя, так как родитель заблокирован другим потомком. То есть, в доме 2 комнаты, одну бронирут - вторая остается свободной, но первая каскадом бронирует родителя, в результате свободную вторую забронировать уже нельзя. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 19:10 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Uniquad, Единицей бронируемого места всегда должна быть наименьшая из возможных, т.е. койка. Тогда домик будет суммой коек, расположенных в нем. В этом случае вы сможете обслужить клиента, которому потребуются, напр., просто 10 коек, и неважно в скольких домиках/комнатах они окажутся. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 29.02.2012, 23:27 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
ЫUniquad, Единицей бронируемого места всегда должна быть наименьшая из возможных, т.е. койка. Тогда домик будет суммой коек, расположенных в нем. В этом случае вы сможете обслужить клиента, которому потребуются, напр., просто 10 коек, и неважно в скольких домиках/комнатах они окажутся. Да, это было бы проще всего. А как раскидывать потом клиентов, чтобы они вдруг не оказались в номере с какими-нибудь левыми чуваками? Или если приезжает большая группа людей, и они хотели бы все остановиться на одном этаже? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 01:49 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Uniquad А как раскидывать потом клиентов, чтобы они вдруг не оказались в номере с какими-нибудь левыми чуваками? Или если приезжает большая группа людей, и они хотели бы все остановиться на одном этаже?Это как раз проще всего реализовать как массовой вставки комнат при бронировании этажа. таблица бронирования содержит ссылки на листья дерева, хуже если по желанию клиента лист дерева превращается в ветку. Вариант softwarera с разделяемыми блокировками частичным бронированием, на более верхних уровнях заслуживает рассмотрения. Только ошибку должен будет возвращать триггер ибо unique по определению не сможет работать на нескольких частичных блокировках (или надо будет разделить таблицы с монопольными блокировками и разделяемыми). А если триггер, то надо глубоко копать pg на предмет когда транзакция начинается, что она видит, и как все это настраивается. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 02:22 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Uniquadsoftwarer, Каскадные блокировки не рулят. Если у родителя есть два потомка, и один потомок каскадом заблокирует родителя, то второй останется незаблокированным, но заблокировать его уже будет нельзя, так как родитель заблокирован другим потомком. То есть, в доме 2 комнаты, одну бронирут - вторая остается свободной, но первая каскадом бронирует родителя, в результате свободную вторую забронировать уже нельзя.Рулят. Блокировки бывают не только монопольными(X), но и разделяемыми(S). На монопольно заблокированный объект нельзя наложить никакую другую блокировку, но разделяемых блокировок может быть на объекте сколько угодно. Комната 1 имеет монопольную блокировку, а дом и выше - разделяемую. Комната 2 - не сможет получить монопольную блокировку, если сдан её угол, так как она уже будет иметь разделяемую блокировку, с которой монопольная несовместима. Если же блокировок на ней нет, то на комнату 2 накладывается монопольная блокировка, а на "предков" - разделяемая. Т.е., на доме в таком случае будет 2 разделяемые блокировки. При снятии блокировки на любую комнату у всех её предков счетчик количества разделяемых уменьшается на 1. Соответственно, дом нельзя заблокировать монопольно, пока на нем есть хоть одна блокировка любого вида. Счетчик разделяемых блокировок необязателен, чисто для удобства. Если 0, значит "внизу" никто не блокировал. В общем, это азы. Можно сделать на любой вкус и цвет, главное хорошо продумать. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 02:26 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Фух, ребята, вроде бы наконец-таки нарыл решение. Всем спасибо за информацию, было очень поучительно для меня пообщаться со знатоками БД. Я в этом вопросе не особо опытен :) Оказалось, что триггер проверки на целостность нужно было явно объявить как constraint trigger deferrable initially deferred. Тогда проверка производится после окончания транзакции, и накладок не возникает. И не понадобились никакие принудительные блокировки. Пока гоняю тесты, и, скрестив пальцы, жду результатов) Но по первым впечатлениям всё работает (тьфу-тьфу-тьфу) :) Если кому интересно, могу сказать, что периоды я реализовал в виде геометрического типа BOX((start_day, start_day), (end_day, end_day)), и проверяю их на пересечение, пользуясь оператором && (start_day/end_day высчитываю, отнимая '2000-01-01' от даты начала бронирования). Кроме того, в отдельном поле типа Array я храню айди всех предков и потомков узла, чтобы каждый раз при проверке целостности не обходить дерево вызовом дополнительных SELECT. Массив с айдишниками предков заполняется автоматически всё теми же триггерами, всё это очень красиво и элегантно выглядит (за это я и люблю программирование :)) ). Я знаю, что поле типа box можно индексировать при помощи gist, и это очень большое ускорение при большом количестве данных, а вот с полем Array пока не разобрался, не знаю, помогут ли здесь индексы для оптимизации. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 03:25 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
И еще, может, кому пригодится: в интернете нашел совет человека, сталкивавшегося с такой же проблемой, но только без иерархической структуры. Он для проверки пересечений обошелся единственным полем BOX, в котором одним из измерений является ID комнаты. Очень элегантное решение, и его же можно использовать в версии postgresql 9.0 с их новой фишкой exclude constraint. Выглядит это как BOX((start_day, room_id), (end_day, room_id+0.5)). 0.5 добавляется, чтобы прямоугольник не оказался нулевой высоты(или ширины). Всё гениальное просто :)) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 03:31 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
Это ППЦ, а не решение, расстрел воробьёв из пушек. Хорошо, если это всего лишь учебное задание, за коммерческое - увольнение без выходного пособия без права работы за компьютером, как минимум, 5 лет. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 04:10 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
ChAЭто ППЦ, а не решение, расстрел воробьёв из пушек. Хорошо, если это всего лишь учебное задание, за коммерческое - увольнение без выходного пособия без права работы за компьютером, как минимум, 5 лет. В чем пипец? Почему воробьев из пушек? Как тогда сделать лучше? Критика неконструктивна, предложи решение лучше :) Про монопольные и разделяемые блокировки я думал, но проблема в том, что у нас есть периоды блокировки, которые начинаются и заканчиваются в произвольное время. Единственное решение, которое я вижу для этой схемы - это создавать запись для каждого отдельного дня. Но тогда количество записей в таблице будет огромным. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 04:39 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
UniquadПро монопольные и разделяемые блокировки я думал, но проблема в том, что у нас есть периоды блокировки, которые начинаются и заканчиваются в произвольное время. Единственное решение, которое я вижу для этой схемы - это создавать запись для каждого отдельного дня. Но тогда количество записей в таблице будет огромным.Прочитал первый топик. Забираю свои слова обратно, в данной задаче блокировки действительно не рулят, точнее рулят, но об этом ниже. Вся необходимая информация уже есть и её можно получить одним, хотя и не самым простым запросом. Иерархическим, если PostgreSQL поддерживает, или простым, но тогда надо "разворачивать" иерархию в структуру данных, которая позволит обойтись неиерархическим запросом. Не надо никаких дополнительных фиксаций бронирования, которое, по сути, ничем не отличается от пребывания, кроме даты начала, которая принадлежит "будущему", и которая либо становится пребыванием, либо "удаляется", т.е., исключается из проверок. Предполагается, что все данные непротиворечивы на момент добавления новой брони, а в момент самого добавления(попытки) просто проверяем, есть ли старая бронь, которая может этому помешать. Собственно всё и не надо никаких ограничений. Если мешает, то наносим отказ, в противном случае - добавляем. Вот в этот момент блокирование может понадобится. Сама же проверка вполне выполнима одним запросом, хотя повторюсь, не самым простым, но на практике вряд ли слишком дорогим, если только дело не касается огромной международной сети. Но даже в этом случае легко ограничить количество данных для проверки идентификатором отеля. Вряд ли кто забронирует всю сеть. И последнее. Какой смысл несёт таблица "bookings" ? Насколько понял задачу, поле "vacancy_id" спокойно можно перенести в таблицу "booking_orders". Бронирование(пребывание) разве не связано 1-к-1 с местом ? Или допустимо бронировать одним ордером одновременно несколько произвольных объектов ? Но тогда даты надо переносить в таблицу "bookings", так как в этом случае на произвольный объект могут быть совершенно разные периоды. Нет ? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 07:13 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
UniquadЫUniquad, Единицей бронируемого места всегда должна быть наименьшая из возможных, т.е. койка. Тогда домик будет суммой коек, расположенных в нем. В этом случае вы сможете обслужить клиента, которому потребуются, напр., просто 10 коек, и неважно в скольких домиках/комнатах они окажутся. Да, это было бы проще всего. А как раскидывать потом клиентов, чтобы они вдруг не оказались в номере с какими-нибудь левыми чуваками? Или если приезжает большая группа людей, и они хотели бы все остановиться на одном этаже? Я бы попробовал сделать так: таблица неделимых единиц бронирования (Койки); таблица с иерархией домик—этаж—комната и т.п. (Вместилища); Каждое Вместилище связано с id своего родителя (можно вместо этого использовать ltree). Койки привязаны к листьям Вместилищ. Бронирование происходит только для Коек, а Вместилища используются только для поиска. Таким образом бронирование Домика №10 превращается в бронирование всех коек в Домике №10 на одно имя, а в интерфейсе это может выглядеть (и называться) бронированием Домика. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 13:17 |
|
||
|
Система бронирования для гостиницы - иерархические ограничения
|
|||
|---|---|---|---|
|
#18+
ChA, Смысл таблицы bookings как раз в том, что один заказ может включать несколько единиц. Период у всех один и тот же. Например, 4 человека хотят приехать в отель с 1 по 10 мая и остановиться в 2-х разных номерах. Но даты я продублировал в bookings (опять же, триггером перед вставкой), чтобы при проверке не обращаться к таблице booking_orders. Для оптимизации. Сейчас моя таблица booking_orders выглядит так: Код: sql 1. 2. 3. 4. 5. 6. 7. Ы, Да, видимо, так будет гораздо проще следить за целостностью данных. Я отказался от этой идее в самом начале, потому что хотелось, чтобы вся работа проводилась "за сценой", чтобы вся процедура бронирования со стороны клиента свелась к простому INSERT INTO booking_orders / INSERT INTO bookings. Интересно, а какие плюсы у такого подхода по сравнению с тем, что я уже реализовал? Ну, кроме того, что можно избавиться от триггера на проверку противоречий и использовать ecxlude constraint. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 01.03.2012, 16:03 |
|
||
|
|

start [/forum/topic.php?fid=32&msg=37685562&tid=1541767]: |
0ms |
get settings: |
5ms |
get forum list: |
15ms |
check forum access: |
2ms |
check topic access: |
2ms |
track hit: |
146ms |
get topic data: |
7ms |
get forum data: |
2ms |
get page messages: |
44ms |
get tp. blocked users: |
1ms |
| others: | 213ms |
| total: | 437ms |

| 0 / 0 |
