У меня типичная проблема производитель-потребитель:
Несколько приложений-производителей записывают запросы на работу в таблицу вакансий в базе данных PostgreSQL.
Запросы на работу имеют поле состояния, которое при создании содержит "Очередь".
Есть несколько потребительских приложений, которые получают уведомление с помощью правила, когда производитель вставляет новую запись:
CREATE OR REPLACE RULE "jobrecord.added" AS
ON INSERT TO jobrecord DO
NOTIFY "jobrecordAdded";
Они попытаются зарезервировать новую запись, установив для нее состояние RESERVED. Конечно, только один потребитель должен добиться успеха. Все остальные потребители не должны иметь возможность резервировать ту же запись. Вместо этого они должны зарезервировать другие записи с состоянием = QUEUED.
Пример: какой-то производитель добавил в таблицу jobrecord следующие записи:
id state owner payload
------------------------
1 QUEUED null <data>
2 QUEUED null <data>
3 QUEUED null <data>
4 QUEUED null <data>
теперь два потребителя A, B хотят их обработать. Они начинают бегать одновременно. Один должен зарезервировать id 1, другой должен зарезервировать id 2, затем первый, кто закончит, должен зарезервировать id 3 и так далее.
В чистом многопоточном мире я бы использовал мьютекс для управления доступом к очереди заданий, но потребителями являются разные процессы, которые могут выполняться на разных машинах. Они обращаются только к одной и той же базе данных, поэтому вся синхронизация должна происходить через базу данных.
Я прочитал много документации о параллельном доступе и блокировке в PostgreSQL, например. http://www.postgresql.org/docs/9.0/interactive/explicit-locking.html Выбрать разблокированную строку в Postgresql PostgreSQL и блокировка
Из этих тем я узнал, что следующий оператор SQL должен делать то, что мне нужно:
UPDATE jobrecord
SET owner= :owner, state = :reserved
WHERE id = (
SELECT id from jobrecord WHERE state = :queued
ORDER BY id LIMIT 1
)
RETURNING id; // will only return an id when they reserved it successfully
К сожалению, когда я запускаю это в нескольких потребительских процессах, примерно в 50% случаев они по-прежнему резервируют одну и ту же запись, обрабатывая ее и перезаписывая изменения другой.
Что мне не хватает? Как мне написать инструкцию SQL, чтобы несколько потребителей не зарезервировали одну и ту же запись?