Postgresql для обновления SKIP LOCKED по-прежнему выбирает дублированные строки
Я использую PostgreSQL в качестве очереди заданий. Ниже приведен мой запрос для получения задания и обновления его состояния:
UPDATE requests AS re
SET
started_at = NOW(),
finished_at = NULL
FROM (
SELECT
_re.*
FROM requests AS _re
WHERE
_re.state = 'pending'
AND
_re.started_at IS NULL
LIMIT 1
FOR UPDATE SKIP LOCKED
) AS sub
WHERE re.id = sub.id
RETURNING
sub.*
теперь у меня есть несколько машин, на каждой машине у меня есть 1 процесс с несколькими потоками, и на каждом потоке у меня есть рабочий. Все работники одного процесса совместно используют пул соединений, обычно имеющий 10-20 соединений.
проблема в том, что приведенный выше запрос вернет несколько строк более одного раза!
Я не могу найти никаких причин. Мог кто поможет?
чтобы быть более подробным, я использую Python3 и psycopg2.
обновление:
я попробовал ответ @a_horse_with_no_name, но, похоже, не работает.
Я заметил, что один запрос извлекается двумя запросами с помощью started_at
обновлена:
2016-04-21 14:23:06.970897+08
и
2016-04-21 14:23:06.831345+08
которые только различаются на 0,14 з.
мне интересно, если в то время, когда эти два соединения выполняют внутренний подзапрос SELECT, обе блокировки еще не установлены?
обновление:
чтобы быть более точным, у меня есть 200 рабочих (т. е. 200 потоков) в 1 процессе на 1 машине.
2 ответов
обратите внимание, что важно, чтобы каждый поток имел свое собственное соединение, если вы не хотите, чтобы они попадали друг в друга.
Если приложение использует несколько потоков выполнения, они не могут совместное использование соединения одновременно. Необходимо либо явно управлять доступ к соединению (с использованием мьютексов) или использование соединения для каждого нитка. Если каждый поток использует свое собственное соединение, вам нужно будет использовать предложение AT, чтобы указать, какое соединение поток будет использовать.
from:http://www.postgresql.org/docs/9.5/static/ecpg-connect.html
все виды wierd вещи происходят, если два потока разделяют одно и то же соединение. Я считаю, что это то, что происходит в вашем случае. При блокировке одного соединения все остальные потоки, использующие одно и то же соединение, будут иметь доступ к заблокированным объектам.
позвольте мне предложить альтернативный подход, это очень просто. Использование Redis для как очередь. Вы можете просто использовать redis-py и методы lpush/rpop или использовать python-rq.
существует вероятность, что транзакция блокировки еще не выпущена во время выбора, или блокировка будет потеряна ко времени, когда результаты выбора будут готовы и начнется инструкция update. Вы пробовали явно начать транзакцию?
BEGIN;
WITH req AS (
SELECT id
FROM requests AS _re
WHERE _re.state = 'pending' AND _re.started_at IS NULL
LIMIT 1 FOR UPDATE SKIP LOCKED
)
UPDATE requests SET started_at = NOW(), finished_at = NULL
FROM req
WHERE requests.id = req.id;
COMMIT;