Для чего нужен FOR UPDATE в PostgreSQL
Что проверяют
Понимаете ли вы, что чтение не блокирует запись, и знаете ли, как защитить сценарий «прочитал, посчитал, записал».
Как отвечать
SELECT ... FOR UPDATE берёт блокировку на выбранные строки до конца транзакции. Другая транзакция, попытавшаяся изменить или заблокировать те же строки, будет ждать.
Зачем это нужно: обычное чтение в PostgreSQL видит снимок данных и никого не блокирует. Поэтому классический сценарий «прочитали остаток, проверили, списали» ломается при параллельном выполнении: обе транзакции прочитают один и тот же остаток и обе спишут. FOR UPDATE превращает это в честную очередь.
begin;
select balance from accounts where id = $1 for update;
-- проверка и расчёт
update accounts set balance = balance - $2 where id = $1;
commit;Обязательно назовите варианты, они и отличают уверенный ответ:
FOR UPDATE SKIP LOCKEDпропускает уже заблокированные строки. Это стандартный способ сделать очередь задач в базе: несколько воркеров берут разные строки и не ждут друг друга;FOR UPDATE NOWAITсразу возвращает ошибку вместо ожидания, когда ждать нельзя;FOR NO KEY UPDATEболее слабая блокировка, не мешающая вставке ссылающихся строк по внешнему ключу;FOR SHAREпозволяет другим читать с блокировкой, но не менять.
И предостережения: блокировки держатся до конца транзакции, поэтому длинная транзакция это очередь из ожидающих; порядок блокировки строк должен быть одинаковым во всех местах кода, иначе появляются взаимные блокировки; для простого инкремента FOR UPDATE не нужен, достаточно атомарного update ... set x = x + 1.
Где спотыкаются
Берут блокировку и делают внутри транзакции вызов внешнего сервиса, растягивая её на секунды.
Пишут очередь на FOR UPDATE без SKIP LOCKED и получают воркеров, стоящих в очереди друг за другом.
Считают, что FOR UPDATE в транзакции с уровнем read committed защищает от всех аномалий: он защищает конкретные прочитанные строки, а не фантомные вставки.