Я пытаюсь внедрить систему назначения задач . Пользователи могут запрашивать задачи из пула. Даже если для параметра SERIALIZABLE установлено значение, транзакция иногда дает одну и ту же задачу нескольким пользователям, даже если это не так.
Упрощенная схема:
CREATE TABLE tasks(
_id CHAR(24) PRIMARY KEY,
totalInstances BIGINT NOT NULL
);
CREATE TABLE assigned(
_id CHAR(24) PRIMARY KEY,
_task CHAR(24) NOT NULL
);
Таблица задач заполнена множеством строк, скажем, у каждой есть totalInstances = 1
, то есть каждая задача должна быть назначена не более одного раза.
Запрос на добавление строки в assigned
:
WITH task_instances AS (
SELECT t._id, t.totalInstances - COUNT(assigned._id) openInstances
FROM tasks t
LEFT JOIN assigned ON t._id = assigned._task
GROUP BY t._id, t.totalInstances
),
selected_task AS (
SELECT _id
FROM task_instances
WHERE openInstances > 0
LIMIT 1
)
INSERT INTO assigned(_id, _task)
SELECT $1, _id
FROM selected_task;
с $1
- случайный идентификатор, передаваемый каждому запросу.
Симптомы
У нас около 100 активных пользователей, регулярно запрашивающих задания. Это работает, как и ожидалось, за исключением, может быть, один раз в 1000 запросов.
Затем две assigned
строки создаются для одного и того же _task
id при параллельных запросах. Я ожидал бы, что сериализуемое выполнение откатит второе, так как openInstances должен был быть уменьшен до 0 первым.
Настройка
Мы используем Postgres 10.3, и запрос запускается из кода Scala через Slick 3.2.3 с withTransactionIsolation(Serializable)
. Никакие другие запросы не удаляются и не вставляются в таблицу assigned
.
Журналы Postgres показывают, что запросы выполняются в разных сеансах и что SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
выполняется перед каждым запросом назначения задачи.
Я попытался переписать запрос в разных стилях, включая VIEW
s для подзапросов WITH
и окружив запрос BEGIN
и COMMIT
, но безрезультатно.
Любая помощь приветствуется.
Редактировать
Я должен добавить, что иногда возникают ожидаемые ошибки / откаты сериализации do , после чего наше приложение повторяет запрос. Я вижу это правильное поведение 10 раз в журналах последних часов, но 2 раза оно по-прежнему ошибочно назначало одну и ту же задачу дважды, как описано выше.