Ответ
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(пропустить заблокированные строки).
Ответ 18+ 🔞
Смотри, вот эта штука SELECT ... FOR UPDATE — это как взять на кассе последний эклер и крикнуть: «ЭТО МОЁ, СУКИ, НЕ ТРОГАТЬ!», пока не расплатишься. По-умному — это блокировка строк в SQL, чтобы никто другой не мог их трогать, пока твоя транзакция не закончится.
Зачем это, блядь, нужно? Ну представь: два процесса одновременно пытаются снять бабки с одного счёта. Оба читают, что там 1000 рублей. Оба вычитают 500. Оба пишут 500. И в итоге на счету 500, а не ноль, как должно было быть. Пиздец, деньги испарились. Чтобы такого не было, ты сначала блокируешь запись, читаешь, меняешь, пишешь — и только потом отпускаешь. Всё по-честному.
Вот тебе живой пример на Go, как это может выглядеть:
// db - это твой *sql.DB
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// На всякий случай подготовим откат, если всё пойдёт по пизде
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(просто пропустить заблокированные строки и работать с тем, что свободно). Очень удобно для всяких очередей заданий.
Короче, инструмент мощный, но если использовать его как слон в посудной лавке — сами знаете, что будет. Пиздец и дедлоки.