> Для чего нужен FOR UPDATE в PostgreSQL (Go, PostgreSQL)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Ozon, Lamoda

Стек: Go, PostgreSQL

> Пример ответа

Короткий ответ

FOR UPDATE в PostgreSQL - это блокировка строк на уровне SELECT, которая предотвращает их изменение или удаление другими транзакциями до завершения текущей. Она используется для реализации pessimistic locking и защиты от гонок при чтении-модификации-записи. Блокировка снимается при COMMIT или ROLLBACK. Важно: она работает только в рамках транзакции и не блокирует обычные SELECT, только UPDATE, DELETE и другие SELECT ... FOR UPDATE.

Подробное объяснение

FOR UPDATE - это одна из row-level lock-опций в PostgreSQL, применяемая к строкам, возвращаемым SELECT. Когда транзакция выполняет SELECT ... FOR UPDATE, она захватывает exclusive lock на каждую выбранную строку. Это означает:

  • Другие транзакции не могут выполнить UPDATE, DELETE или SELECT ... FOR UPDATE на этих строках - они будут ждать, пока текущая транзакция завершится.
  • Обычные SELECT (без FOR UPDATE) продолжают работать, так как они используют MVCC и не блокируются.
  • Блокировка снимается только при COMMIT или ROLLBACK транзакции.

Основное назначение - решение проблемы lost update и гонок при паттерне read-modify-write. Например, если два запроса одновременно читают счёт, изменяют его и записывают, без блокировки одно изменение потеряется. FOR UPDATE гарантирует, что только одна транзакция может работать с конкретной строкой одновременно.

Важные нюансы:

  • FOR UPDATE работает только внутри транзакции (BEGIN/COMMIT).
  • Можно комбинировать с SKIP LOCKED - пропустить заблокированные строки, полезно для очередей задач.
  • NOWAIT - немедленно вернуть ошибку, если строка заблокирована, вместо ожидания.
  • Блокировка применяется к строкам, которые реально возвращает запрос, включая строки из JOIN (если не указано OF table).
  • В PostgreSQL 9.5+ есть FOR UPDATE OF table для ограничения блокировки конкретной таблицей.
  • При использовании индексов блокировка всё равно применяется к строкам таблицы, не к индексам.

На практике

В Go с драйвером pgx или database/sql FOR UPDATE используется в связке с транзакцией. Типичный сценарий - обработка заказов, балансов, инвентаря, где нужно атомарно прочитать и обновить состояние.

Важно помнить про deadlocks: если две транзакции блокируют строки в разном порядке, они могут взаимно ждать друг друга. PostgreSQL обнаруживает deadlock и откатывает одну из транзакций с ошибкой SQLSTATE 40P01.

Также стоит учитывать, что FOR UPDATE увеличивает время удержания блокировок и может снизить конкурентность. Для высоконагруженных систем часто предпочитают optimistic locking (версионирование) или SKIP LOCKED для очередей.

Пример кода

GO
// Использование pgx
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
var balance int
err = tx.QueryRow(ctx,
"SELECT balance FROM accounts WHERE id = $1 FOR UPDATE",
accountID,
).Scan(&balance)
if err != nil {
return err
}
newBalance := balance - amount
if newBalance < 0 {
return errors.New("insufficient funds")
}
_, err = tx.Exec(ctx,
"UPDATE accounts SET balance = $1 WHERE id = $2",
newBalance, accountID,
)
if err != nil {
return err
}
return tx.Commit(ctx)

Как отвечать на собеседовании

Начните с краткого определения: FOR UPDATE - это row-level lock для SELECT. Затем объясните, зачем он нужен: защита от гонок при read-modify-write. Упомяните, что работает только в транзакции и снимается при COMMIT/ROLLBACK.

Покажите понимание MVCC: обычные SELECT не блокируются, только пишущие операции. Расскажите про SKIP LOCKED и NOWAIT как модификаторы для специфичных сценариев. Если спросят про производительность - честно скажите, что это pessimistic locking, он снижает конкурентность, и в некоторых случаях лучше optimistic подход.

Хорошо привести пример из практики: баланс, заказ, очередь задач. Покажите, как в Go это выглядит с транзакцией. Если интервьюер спросит про deadlock - объясните, что PostgreSQL сам их детектит и откатывает одну из транзакций.

Что проверяет интервьюер

  • Понимание отличий между MVCC и блокировками.
  • Знание, когда нужен pessimistic locking, а когда достаточно optimistic.
  • Умение работать с транзакциями в реальном коде.
  • Понимание trade-off: блокировки vs конкурентность.
  • Знание модификаторов SKIP LOCKED, NOWAIT, OF.
  • Осознание рисков deadlock и способов их минимизации.

Типичные ошибки

  • Говорить, что FOR UPDATE блокирует чтение - это не так, обычные SELECT не блокируются.
  • Забывать, что блокировка снимается только при COMMIT/ROLLBACK, а не после выполнения запроса.
  • Использовать FOR UPDATE без транзакции - в PostgreSQL это ошибка или бессмысленно.
  • Не упоминать SKIP LOCKED для очередей - это частый сценарий, который ждут от senior.
  • Путать FOR UPDATE с table-level lock (например, LOCK TABLE).
  • Не учитывать, что при JOIN блокируются строки всех таблиц, если не указано OF.
  • Думать, что FOR UPDATE решает все проблемы конкурентности - на практике нужно проектировать с учётом длительности транзакций.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью