Очередь или возможность "запирания" в PHP или MySQL
Есть некая таблица queue, она по запросу отдает unique.ciphertext и "запирает" этот ciphertext на определенное время. Чтобы по другим запросам не возвращало данные записи. Это достигалось так: у каждой записи есть поле blockedtill, после считывания идет обновление blockedtill = now() + interval 2 minute.
Проблема: Одновременно работают 10+ потоков, и приблизительно в 40% "замыкание" не срабатывает, так как следующие запросы успевают получить те же ciphertext до того как запишется новое значение в blockedtill.
Пытался решить проблему так, не сработало:
BEGIN;
LOCK TABLES `queue` WRITE;
SELECT `ciphertext` FROM `queue` WHERE ...;
UPDATE `queue` SET `blockedtill` = NOW() + INTERVAL 2 MINUTE WHERE ...;
COMMIT;
UNLOCK TABLES;
Подскажите пожалуйста куда копать или есть ли готовая реализация очереди на PHP, MySQL?
Ответы (1 шт):
Это обычное резервирование за потоком.
Легко решается введением поля processed_by, которое содержит NULL, если запись ещё никем не взята, -1, если запись уже обработана, и значение CONNECTION_ID(), если она обрабатывается соединением.
Соответственно чтобы взять запись на обработку, выполняем
UPDATE table
SET processed_by = CONNECTION_ID()
-- ORDER BY {expression}
LIMIT 1;
после чего проверяем, получилось или нет, запросом
SELECT *
FROM table
WHERE processed_by = CONNECTION_ID();
Возможные варианты:
- ноль записей - значит, запись перехватил конкурент. повторяем попытку резервирования;
- одна запись - резервирование успешно, приступаем к обработке;
- более одной записи - проблема, зовём оператора.
При этом blocked_till не требуется. Однако в структуре должно быть автообновляемое поле updated_at. Его назначение - по нему определить, что запись "подвисла" (зарезервировавший запись поток не выполнил обработку по сбою или иным причинам). И соответственно такие записи возвращаются в пул необработанных из Event procedure сбросом поля processed_by в NULL.