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;