Ответ
SELECT ... FOR UPDATE
— это SQL-конструкция, которая позволяет заблокировать строки, возвращаемые запросом, до завершения текущей транзакции. Это необходимо для предотвращения состояния гонки (race condition) в операциях типа «прочитать-изменить-записать» (read-modify-write).
Основной сценарий использования: когда вам нужно прочитать значение из строки, выполнить на его основе какие-то вычисления, а затем обновить эту же строку, гарантируя, что никто другой не изменил её между вашим чтением и записью.
Пример на Go: обновление баланса пользователя.
// db - это ваш *sql.DB
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// defer tx.Rollback() гарантирует откат транзакции, если что-то пойдет не так
defer tx.Rollback()
var balance int
// Блокируем строку с id=123 до конца транзакции
err = tx.QueryRow("SELECT balance FROM accounts WHERE id = $1 FOR UPDATE", 123).Scan(&balance)
if err != nil {
// Если строка уже заблокирована другой транзакцией, здесь может быть ошибка или ожидание
log.Fatal(err)
}
// Выполняем бизнес-логику
newBalance := balance + 100
// Обновляем данные в той же транзакции
_, err = tx.Exec("UPDATE accounts SET balance = $1 WHERE id = $2", newBalance, 123)
if err != nil {
log.Fatal(err)
}
// Если все успешно, фиксируем изменения
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
Ключевые моменты и риски:
- Область действия: Блокировка действует только в рамках транзакции и снимается после
COMMIT
илиROLLBACK
. - Взаимоблокировки (Deadlocks): Неправильное использование может привести к дедлокам, когда две транзакции ждут друг друга, заблокировав нужные каждой ресурсы. Важно, чтобы транзакции блокировали ресурсы в одном и том же порядке.
- Производительность: Блокировки снижают параллелизм и могут стать узким местом в системе. Используйте их только там, где это действительно необходимо.
- Вариации: Некоторые СУБД, например PostgreSQL, поддерживают опции
NOWAIT
(немедленно вернуть ошибку, если строка заблокирована) иSKIP LOCKED
(пропустить заблокированные строки).