Postgres обновление после выбора - PullRequest
21 голосов
/ 20 июля 2011

Я хочу сделать следующее за один раз:

SELECT * FROM jobs WHERE status='PENDING';
UPDATE jobs SET status='RUNNING' WHERE status='PENDING';

Получите все ожидающие задания, а затем сразу установите их как «РАБОТАЮЩИЕ».

Причина, по которой я не хочу делать это одно за другим в двух инструкциях, заключается в том, что задания могут быть добавлены в таблицу заданий как «В ожидании» после SELECT, но до ОБНОВЛЕНИЯ, так что в итоге я бы настроил задание как Запуск, хотя я не схватил его, пока он находился в состоянии ожидания.

Есть ли способ сделать это в одном? Поэтому я хочу, чтобы результат SELECT и ОБНОВЛЕНИЕ происходили на лету.

Спасибо.

Ответы [ 3 ]

46 голосов
/ 20 июля 2011

Почему бы не использовать предложение RETURNING и обрабатывать обе вещи в одном операторе:

UPDATE jobs 
    SET status='RUNNING' 
WHERE status='PENDING'
RETURNING *

Таким образом, вы получите все строки, которые были изменены UPDATE, с помощью одной атомарной операции.

16 голосов
/ 20 июля 2011

В общем, вы должны сделать это с одним оператором UPDATE. На UPDATE обычно не влияют строки, которые могли измениться во время выполнения оператора UPDATE, однако, полезно прочитать об уровнях изоляции транзакций здесь .

Предполагается, что вы используете настройку Read Committed по умолчанию, вот что она говорит:

Read Committed - уровень изоляции по умолчанию в PostgreSQL. Когда транзакция выполняется на этом уровне изоляции, запрос SELECT видит только данные зафиксированы до начала запроса;

А что касается ОБНОВЛЕНИЯ:

Команды UPDATE, DELETE, SELECT FOR UPDATE и SELECT FOR SHARE ведут себя так же, как SELECT с точки зрения поиска целевых строк: они найдет только те строки назначения, которые были зафиксированы на момент запуска команды время. Однако такая целевая строка, возможно, уже была обновлена ​​(или удален или заблокирован) другой параллельной транзакцией к тому времени, когда она найденный. В этом случае потенциальный обновитель будет ждать первого обновление транзакции для фиксации или отката (если она все еще находится в прогресс). Если первый апдейтер откатывается, то его эффекты отрицается, и второй обновитель может продолжить обновление изначально найденный ряд. Если первый обновитель фиксирует, второй обновитель фиксирует проигнорирует строку, если первый обновитель удалил ее, в противном случае она будет попытаться применить свою операцию к обновленной версии строки. условие поиска команды (предложение WHERE) переоценивается в посмотреть, соответствует ли обновленная версия строки поиску состояние. Если это так, второй модуль обновления продолжает свою работу, начиная с обновленной версии строки. (В случае выбора Для обновления и выбора для обмена, это означает, что это обновленная версия строки, которая заблокирована и возвращена клиенту.)

Так что в вашем сценарии, одно ОБНОВЛЕНИЕ должно быть хорошо.

Имейте также в виду, что существует так называемый оператор SELECT FOR UPDATE, который блокирует выбранные вами строки. Вы можете прочитать об этом здесь . Сценарий, в котором вам нужно будет использовать эту функцию, будет в системе бронирования. Рассмотрим этот пример:

  1. Выполните SELECT, чтобы узнать, доступен ли номер XYZ для бронирования на дату X.
  2. Помещение доступно. Выполните UPDATE запрос, чтобы забронировать комнату.

Видите ли вы здесь потенциальную проблему? Если между шагами 1 и 2 комната забронирована другой транзакцией, то когда мы достигаем шага 2, мы действуем в предположении, которое больше не действует, а именно, что комната доступна.

Однако, если на шаге 1 вместо этого мы используем оператор SELECT FOR UPDATE, мы гарантируем, что никакая другая транзакция не сможет заблокировать эту строку, поэтому, когда мы перейдем к UPDATE строки, мы знаем, что это безопасно.

Но, опять же, в вашем сценарии этот SELECT FOR UPDATE не нужен, потому что вы делаете все в одном выражении и ничего не проверяете заранее.

5 голосов
/ 20 июля 2011
begin;
select * 
from jobs 
where status='pending'
for update
;
update jobs 
set status='running' 
where status='pending';
commit;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...