Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Опции SELECT FOR UPDATE в Go (PostgreSQL)
В Go при работе с транзакциями и конкурентным доступом к данным часто используется SELECT FOR UPDATE. Это пессимистическая блокировка, которая предотвращает модификацию выбранных строк другими транзакциями до завершения текущей. Рассмотрим основные опции и их применение.
Ключевые опции FOR UPDATE
1. FOR UPDATE
Базовая блокировка строк для обновления. Другие транзакции не могут изменять или блокировать эти строки до коммита.
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;
2. FOR NO KEY UPDATE
Более мягкая блокировка: разрешает другим транзакциям блокировать строки FOR KEY SHARE (например, при ссылочной целостности). Не блокирует операции, затрагивающие только внешние ключи.
SELECT * FROM products WHERE stock > 0 FOR NO KEY UPDATE;
3. FOR SHARE
Блокировка для чтения: другие транзакции могут тоже заблокировать строки FOR SHARE, но не могут изменить их. Полезна для совместного чтения с гарантией неизменности данных.
SELECT * FROM accounts WHERE balance > 1000 FOR SHARE;
4. FOR KEY SHARE
Самая слабая блокировка: защищает только от изменений значений ключей. Другие транзакции могут менять неключевые столбцы. Используется для поддержания ссылочной целостности.
SELECT * FROM customers WHERE id = 5 FOR KEY SHARE;
Дополнительные модификаторы
NOWAIT
Если строки уже заблокированы другой транзакцией, запрос сразу завершится ошибкой вместо ожидания.
// Пример в Go с github.com/jackc/pgx
_, err := tx.Query(ctx, "SELECT * FROM orders FOR UPDATE NOWAIT")
if err != nil {
// Обработка ошибки блокировки
}
SKIP LOCKED
Пропускает уже заблокированные строки, возвращая только доступные. Полезно для реализации очередей задач.
SELECT * FROM task_queue WHERE processed = false FOR UPDATE SKIP LOCKED LIMIT 10;
Практическое применение в Go
При работе с поддержкой транзакций в Go (например, с библиотеками pgx, sqlx), важно учитывать:
- Уровни изоляции транзакций:
SELECT FOR UPDATEобычно используется сRepeatable ReadилиSerializable. - Время удержания блокировки: блокировка действует до завершения транзакции (коммит или откат).
- Индексы и производительность: блокировка работает эффективнее при выборке по индексируемым полям.
// Пример транзакции с FOR UPDATE
func ReserveProduct(ctx context.Context, tx pgx.Tx, productID int) error {
var stock int
// Блокировка строки для изменения
err := tx.QueryRow(ctx,
"SELECT stock FROM products WHERE id = $1 FOR UPDATE",
productID,
).Scan(&stock)
if err != nil {
return err
}
if stock <= 0 {
return errors.New("товар отсутствует")
}
// Изменение заблокированной строки
_, err = tx.Exec(ctx,
"UPDATE products SET stock = stock - 1 WHERE id = $1",
productID,
)
return err
}
Типичные сценарии использования
- Финансовые операции: блокировка счетов перед списанием/зачислением
- Управление запасами: резервирование товаров
- Очереди задач: конкурентная обработка задач с SKIP LOCKED
- Сложные агрегации: блокировка данных для консистентных отчетов
Важные предостережения
- Дедлоки: неправильный порядок блокировок может привести к взаимным блокировкам
- Производительность: длительные блокировки снижают параллелизм
- Изоляция: в режиме
Read Committedвозможны фантомные чтения
В Go особенно важно всегда закрывать транзакции (коммит или роллбэк) и использовать контексты с таймаутами для предотвращения вечных блокировок.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := pgx.BeginFunc(ctx, db, func(tx pgx.Tx) error {
// Использование FOR UPDATE с NOWAIT для быстрого отказа
return ReserveProductWithTimeout(ctx, tx, productID)
})
Выбор конкретной опции зависит от требований конкурентности, целостности данных и производительности вашего приложения.