Ответ
Конструкция SELECT FOR UPDATE
используется для пессимистической блокировки строк в транзакции. В PostgreSQL, который часто используется с Go, доступны следующие опции для управления поведением блокировки:
NOWAIT
- Назначение: Немедленно вернуть ошибку, если запрашиваемые строки уже заблокированы другим процессом. Это полезно, когда ожидание нежелательно.
- SQL:
SELECT * FROM products WHERE id = 1 FOR UPDATE NOWAIT;
SKIP LOCKED
- Назначение: Пропустить уже заблокированные строки и вернуть только те, которые удалось заблокировать. Идеально подходит для реализации очередей задач, где несколько воркеров могут параллельно разбирать задачи из одной таблицы.
- SQL:
SELECT * FROM tasks WHERE status = 'new' LIMIT 10 FOR UPDATE SKIP LOCKED;
FOR NO KEY UPDATE
- Назначение: Более слабая блокировка, чем
FOR UPDATE
. Она блокирует строку от изменений (UPDATE
,DELETE
), но не препятствует другим транзакциям выполнятьSELECT FOR KEY SHARE
. Используется, когда нужно предотвратить изменение данных, но не блокировать чтение по внешним ключам.
- Назначение: Более слабая блокировка, чем
FOR SHARE
/FOR KEY SHARE
- Назначение: Это разделяемые (shared) блокировки.
FOR SHARE
предотвращает изменение строки другими транзакциями, но позволяет им читать её сFOR SHARE
.FOR KEY SHARE
— ещё более слабая блокировка, используемая для проверки ссылочной целостности.
- Назначение: Это разделяемые (shared) блокировки.
Пример использования в Go (с pgx
):
Важно помнить, что FOR UPDATE
работает только внутри транзакции.
tx, err := conn.Begin(ctx)
if err != nil {
// обработка ошибки
}
defer tx.Rollback(ctx) // Откатываем транзакцию в случае ошибки
var productName string
var quantity int
// Блокируем строку товара, чтобы безопасно обновить остаток
err = tx.QueryRow(ctx,
"SELECT name, quantity FROM products WHERE id = $1 FOR UPDATE", productID).
Scan(&productName, &quantity)
if err != nil {
// Обработка ошибки (включая pgerrcode.LockNotAvailable если используется NOWAIT)
return
}
// ... логика изменения данных ...
_, err = tx.Exec(ctx, "UPDATE products SET quantity = $1 WHERE id = $2", newQuantity, productID)
if err != nil {
return
}
err = tx.Commit(ctx) // Фиксируем транзакцию