Если вы единственный пользователь, запрос будет в порядке. В частности, внутри самого запроса (между внешним запросом и подзапросом) нет состояния гонки или взаимоблокировки. Я цитирую руководство здесь:
Однако транзакция никогда не конфликтует сама с собой.
Для одновременного использования вопрос может быть более сложным. Вы будете в безопасности, выбрав SERIALIZABLE
режим транзакции а>:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
RETURNING *
COMMIT;
Вам необходимо подготовиться к сбоям сериализации и повторить запрос в таком случае.
Но я не совсем уверен, не перебор ли это. Я попрошу @kgrittn зайти .. он эксперт в параллелизме и сериализуемых транзакциях ..
И он это сделал. :)
Лучшее из обоих миров
Выполните запрос в режиме транзакции по умолчанию READ COMMITTED
.
Для Postgres 9.5 или новее используйте FOR UPDATE SKIP LOCKED
. Видеть:
Для более старых версий перепроверьте условие computed IS NULL
явно во внешнем UPDATE
:
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
AND computed IS NULL;
Как сообщил @kgrittn в комментарии к своему ответу, этот запрос может оказаться пустым, ничего не сделав, в (маловероятном) случае, если он будет связан с параллельной транзакцией.
Следовательно, он будет работать так же, как первый вариант в режиме транзакции SERIALIZABLE
, вам придется повторить попытку - только без потери производительности.
Единственная проблема: хотя конфликт очень маловероятен, потому что окно возможностей очень мало, оно может произойти при большой нагрузке. Вы не могли точно сказать, не осталось ли, наконец, строк.
Если это не имеет значения (как в вашем случае), на этом все готово.
Если это так, для полной уверенности начните еще один запрос с явная блокировка после получения пустой результат. Если он окажется пустым, все готово. Если нет, продолжайте.
В plpgsql это могло бы выглядеть так:
LOOP
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE SKIP LOCKED); -- pg 9.5+
-- WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
-- AND computed IS NULL; -- pg 9.4-
CONTINUE WHEN FOUND; -- continue outside loop, may be a nested loop
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE);
EXIT WHEN NOT FOUND; -- exit function (end)
END LOOP;
Это должно дать вам лучшее из обоих миров: производительность и надежность.
person
Erwin Brandstetter
schedule
18.07.2012