В чем разница между SELECT FOR UPDATE и обычным SELECT и когда применять SELECT FOR UPDATE
Что проверяют
Понимаете ли вы, как база ведёт себя при конкурентной записи, и умеете ли защититься от потерянного обновления. Следом почти всегда спрашивают, как это выглядит в Spring и JPA и чем пессимистическая блокировка отличается от оптимистической.
Как отвечать
Обычный SELECT в PostgreSQL и в InnoDB (MySQL) читает снимок данных и никого не блокирует: пока вы думаете над прочитанным балансом, другая транзакция успеет его изменить. SELECT ... FOR UPDATE ставит на найденные строки блокировку до конца транзакции. Чужие UPDATE, DELETE и FOR UPDATE по тем же строкам ждут, а простое чтение проходит.
Когда он нужен:
- чтение, решение, запись: прочитали остаток, проверили в Java-коде, что хватает, списали. Без блокировки два параллельных запроса оба увидят старое значение;
- очередь задач в таблице:
FOR UPDATE SKIP LOCKEDпозволяет нескольким воркерам разбирать строки, не мешая друг другу; - не ждать:
NOWAITсразу бросает ошибку, если строка занята.
Работает это только внутри транзакции: в режиме autocommit блокировка снимается сразу после запроса. В Spring Data JPA метод репозитория помечают @Lock(LockModeType.PESSIMISTIC_WRITE) и вызывают из @Transactional-сервиса, Hibernate добавит к запросу блокировку строк. Таймаут ожидания задают подсказкой jakarta.persistence.lock.timeout.
Альтернатива: оптимистическая блокировка через @Version. Строку не держат, а при записи сверяют версию и ловят OptimisticLockException. Подходит, когда конфликты редкие.
Где спотыкаются
Приводят пример UPDATE accounts SET balance = balance - 100, которому FOR UPDATE не нужен: такой UPDATE и так атомарен. Блокировка нужна, когда между чтением и записью есть логика в приложении.
Забывают про дедлоки. Если две транзакции блокируют строки в разном порядке, база прервёт одну из них. Лечится единым порядком захвата, например по возрастанию id.
Держат блокировку долго: внутри транзакции ходят во внешний API, и остальные запросы стоят в очереди. Транзакция с FOR UPDATE должна быть короткой.