Im создание веб-приложения – система бронирования с использованием php и mysql. Система позволит пользователям делать резервирование временных интервалов на некоторых устройствах (время пользователя, работающее на этом устройстве).
Я называю эти временные интервалы времени. Слоты хранятся в таблице базы данных mysql следующим образом:
CREATE TABLE IF NOT EXISTS `slot` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `start` int(11) unsigned DEFAULT NULL, `end` int(11) unsigned DEFAULT NULL, `uid` int(11) unsigned DEFAULT NULL, `group` int(11) unsigned DEFAULT NULL, `message` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `devices_id` int(11) unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `start_2` (`start`), UNIQUE KEY `end_2` (`end`), KEY `index_foreignkey_slot_devices` (`devices_id`), KEY `start` (`start`), KEY `end` (`end`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=6997 ;
(эта таблица создается автоматически с помощью redbean orm, и я еще не оптимизировал ее)
Поэтому, когда пользователь создает резервирование, в эту таблицу вставляется новая строка. В начале и конце столбца я сохраняю временные метки unix для начала и конца каждого резервирования.
Еще одна вещь, о которой следует помнить, – это то, что приложение позволяет различным пользователям видеть разные расписания одного и того же устройства. Например: у пользователя A есть интервалы в 6 минут, чтобы она могла видеть свободный слот (12:00 – 12:06), а также свободный слот (12:06 – 12:12), но пользователь B имеет 4 интервала в течение нескольких минут, поэтому он также среди других видит слот (12:04 – 12:08). Каждый пользователь или группа пользователей могут иметь разные интервальные интервалы. Поэтому я должен быть уверен, что когда оба пользователя A и B отправят запрос с этими слотами, только один из них будет успешным. Это подводит меня к транзакциям, а также к моему вопросу.
Я делаю это следующим образом: – начать транзакцию – выберите все слоты алгоритма этого дня, который проверяет временные конфликты между выбранными зарезервированными слотами и запрошенными слотами – если в таблице слотов нет столкновений, добавьте новые строки, иначе ошибка сигнала пользователю – совершить
Теперь вы знаете, что может произойти, когда оно работает одновременно. Я новичок в транзакциях и mysql, но попытался проверить его, и у меня есть причина полагать, что просто быть в транзакции недостаточно в этом случае, но Im не уверен.
Поэтому мой вопрос: как я могу выбрать, проверить наличие коллизий и сохранить резервирование в одной транзакции должным образом.
благодаря
Вам нужна блокировка . Сделки действительно «не нужны».
Вы можете выбирать между «пессимистической блокировкой» и «оптимистичной блокировкой». Решение о том, какая из этих двух возможностей зависит от вас, и должна оцениваться в основном с учетом:
Я рекомендую прочитать эти два, чтобы создать представление о вовлеченных вещах:
Возможно, это не так элегантно, но это всего лишь пример, показывающий, как можно делать все без транзакции (и даже без ограничений UNIQUE). Что нужно сделать, так это использовать следующую объединенную статграмму INSERT + SELECT и после ее выполнения проверить количество затронутых строк. Если количество затронутых строк равно 1, то оно было выполнено иначе (если оно равно 0) произошло столкновение, а другая сторона выиграла.
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT @startTime, @endTime, @uid, @group, @message, @deviceId FROM `slot` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= @endTime AND `end` >= @startTime AND `devices_id` = @deviceId) GROUP BY (1);
Это пример оптимистического блокирования, полученного без транзакций и с помощью одной операции SQL.
Как написано, проблема состоит в том, что для работы в ней должна быть хотя бы одна строка, уже работающая (иначе оператор SELECT всегда будет возвращать пустой набор записей, и в этом случае ничего не будет вставлено evei, если нет столкновений. Существуют две возможности сделать это фактически:
перепишите так, что основное предложение FROM ссылается на любую таблицу, которая имеет хотя бы одну строку или лучше создать одну маленькую таблицу (возможно, названную dummy
) с одним столбцом и только одной записью в ней и переписать следующим образом (обратите внимание, что больше не требуется предложение GROUP BY)
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT @startTime, @endTime, @uid, @group, @message, @deviceId FROM `dummy` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= @endTime AND `end` >= @startTime AND `devices_id` = @deviceId);
Вот следуя серии инструкций, которые, если вы просто копируете / вставляете, показывают идею в действии. Я предположил, что вы кодируете дату / время в полях int как число с цифрами даты и времени, которые связаны друг с другом.
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) VALUES (1008141200, 1008141210, 11, 2, 'Dummy Record', 14) INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT 1408141206, 1408141210, 11, 2, 'Hello', 14 FROM `slot` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= 1408141210 AND `end` >= 1408141206 AND `devices_id` = 14) GROUP BY (1); INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT 1408141208, 1408141214, 11, 2, 'Hello', 14 FROM `slot` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= 1408141214 AND `end` >= 1408141208 AND `devices_id` = 14) GROUP BY (1); INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT 1408141216, 1408141220, 11, 2, 'Hello', 14 FROM `slot` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= 1408141220 AND `end` >= 1408141216 AND `devices_id` = 14) GROUP BY (1); SELECT * FROM `slot`;
Это, безусловно, крайний пример Optimistic Locking, но очень эффективен в конце, потому что все выполняется только с одной инструкцией SQL и с низким взаимодействием (обмен данными) между сервером базы данных и php-кодом. Кроме того, практически нет «реальной» блокировки.
Один и тот же код может стать хорошей реализацией Pessimistc Locking, просто связанной с явными инструкциями по блокировке / разблокировке таблицы:
LOCK TABLE slot WRITE, dummy READ; INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT @startTime, @endTime, @uid, @group, @message, @deviceId FROM `dummy` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= @endTime AND `end` >= @startTime AND `devices_id` = @deviceId); UNLOCK TABLES;
Конечно, в этом случае (Пессимистическая блокировка) SELECT и INSERT могут быть разделены и некоторый код php выполняется между ними. Однако этот код остается очень быстрым (без обмена данными с php, без промежуточного php-кода), и поэтому продолжительность Пессимистической блокировки является самой короткой. Хранение пессимистической блокировки как можно короче – ключевой момент, чтобы избежать замедления работы приложения.
В любом случае вам нужно проверить количество возвращаемых значений затронутых записей, чтобы узнать, удалось ли это, так как код практически одинаковый, и таким образом вы получите информацию об успехе / сбое.
Здесь http://dev.mysql.com/doc/refman/5.0/en/insert-select.html они говорят, что «MySQL не разрешает одновременные вставки для инструкций INSERT … SELECT», поэтому не нужно пессимистично Lock, но в любом случае это может быть хорошим вариантом, если вы считаете, что это изменится в будущих версиях MySQL.
Я «Оптимистичен», что это не изменится 😉