WAIT в Oracle SQL. Введение
Есть слова в Oracle, которые выглядят скромно, а на деле управляют очень важной вещью — конкуренцией.
WAIT как раз из таких. Оно появляется там, где ты работаешь с блокировками и хочешь не просто “попробовать и упасть”, а дать сессии время дождаться нужного ресурса.
Чаще всего ты встретишь его рядом с FOR UPDATE,
NOWAIT,
SELECT,
UPDATE и транзакционной логикой.
Люблю эту тему за взрослость: здесь SQL уже не только про данные, но и про координацию процессов.
Синтаксис
SELECT ...
FROM table_name
FOR UPDATE WAIT nСмысл простой: если нужная строка уже заблокирована другой сессией,
Oracle не будет немедленно выбрасывать ошибку как при
NOWAIT,
а подождёт указанное количество секунд.
Если ресурс освободится — запрос продолжит работу.
Если нет — получишь ошибку таймаута по блокировке.
Это особенно полезно в очередях задач, сервисных обработчиках, ручных корректировках и сценариях, где краткое ожидание лучше немедленного отказа.
Где используют
- обработка строк через FOR UPDATE в конкурентной среде
- очереди задач и воркеры
- ручные транзакции в операционных системах
- сервисные процедуры, где допустимо короткое ожидание блокировки
- управление конфликтами между несколькими сессиями
- альтернатива агрессивному NOWAIT
100 примеров
1. Базовое ожидание блокировки строки на 5 секунд
SELECT order_id,status
FROM orders
WHERE order_id = 1001
FOR UPDATE WAIT 52. Ожидание при захвате клиента для редактирования
SELECT customer_id,customer_name
FROM customers
WHERE customer_id = 10
FOR UPDATE WAIT 33. Блокировка строки платежа с коротким таймаутом
SELECT payment_id,amount
FROM payments
WHERE payment_id = 555
FOR UPDATE WAIT 24. Ожидание при корректировке остатка товара
SELECT sku,qty
FROM stock
WHERE sku = 'A100'
FOR UPDATE WAIT 45. Захват строки задачи перед обработкой
SELECT task_id,status
FROM tasks
WHERE task_id = 77
FOR UPDATE WAIT 106. Ожидание строки счёта перед обновлением суммы
SELECT invoice_id,total_amt
FROM invoices
WHERE invoice_id = 7001
FOR UPDATE WAIT 67. Блокировка активной подписки
SELECT sub_id,plan_code
FROM subscriptions
WHERE sub_id = 900
FOR UPDATE WAIT 58. Захват строки по email в customer support
SELECT user_id,email
FROM users
WHERE email = 'demo@example.com'
FOR UPDATE WAIT 89. Ожидание при корректировке статуса доставки
SELECT shipment_id,status
FROM shipments
WHERE shipment_id = 321
FOR UPDATE WAIT 710. Блокировка одной строки по составному фильтру
SELECT order_id,user_id
FROM orders
WHERE user_id = 15
AND order_id = 9001
FOR UPDATE WAIT 311. Ожидание очереди возврата
SELECT refund_id,status
FROM refunds
WHERE refund_id = 801
FOR UPDATE WAIT 1212. Захват строки маршрута для перерасчёта ETA
SELECT route_id,eta_min
FROM route_eta
WHERE route_id = 5
FOR UPDATE WAIT 413. Блокировка профиля пользователя перед изменением логина
SELECT user_id,login
FROM user_login
WHERE user_id = 42
FOR UPDATE WAIT 514. Ожидание строки промокода
SELECT promo_code,used_cnt
FROM promo_codes
WHERE promo_code = 'SALE10'
FOR UPDATE WAIT 915. Захват строки банковского счёта
SELECT account_id,balance
FROM accounts
WHERE account_id = 2020
FOR UPDATE WAIT 516. Блокировка строки по дате и коду
SELECT ccy,rate_date,rate
FROM fx_rates
WHERE ccy = 'USD'
AND rate_date = DATE '2026-03-22'
FOR UPDATE WAIT 617. Ожидание задачи определённого воркера
SELECT task_id,worker_id
FROM tasks
WHERE worker_id = 3
AND status = 'NEW'
FOR UPDATE WAIT 218. Захват активной сессии пользователя
SELECT session_id,last_seen
FROM user_sessions
WHERE session_id = 'S100'
FOR UPDATE WAIT 519. Блокировка документа перед архивированием
SELECT doc_id,status
FROM docs
WHERE doc_id = 777
FOR UPDATE WAIT 1520. Захват записи журнала для повторной отправки
SELECT msg_id,send_status
FROM notif_queue
WHERE msg_id = 12345
FOR UPDATE WAIT 4Еще 20 примеров
21. Блокировка нескольких строк одного клиента
SELECT order_id,status
FROM orders
WHERE user_id = 15
FOR UPDATE WAIT 522. Захват строки с сортировкой по дате
SELECT order_id,order_date
FROM orders
WHERE user_id = 15
ORDER BY order_date DESC
FOR UPDATE WAIT 623. Ожидание строки с ограничением через FETCH FIRST
SELECT task_id,status
FROM tasks
WHERE status = 'NEW'
ORDER BY task_id
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 324. Захват первой активной подписки
SELECT sub_id,user_id
FROM subscriptions
WHERE status = 'ACTIVE'
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 425. Выбор строки для очереди выплат
SELECT payout_id,status
FROM payouts
WHERE status = 'PENDING'
ORDER BY payout_id
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 526. Блокировка по месячному ключу
SELECT ym_key,total_amt
FROM month_totals
WHERE ym_key = 202603
FOR UPDATE WAIT 227. Захват строки с фильтром по дате
SELECT event_id,event_ts
FROM events
WHERE event_ts >= TRUNC(SYSDATE)
FOR UPDATE WAIT 528. Ожидание строки с join-фильтром через WHERE CURRENT OF не нужно, только захват
SELECT o.order_id,o.status
FROM orders o
JOIN users u ON u.user_id = o.user_id
WHERE u.email = 'demo@example.com'
FOR UPDATE OF o.status WAIT 429. Блокировка только колонки целевой таблицы через OF
SELECT o.order_id,o.status,u.email
FROM orders o
JOIN users u ON u.user_id = o.user_id
WHERE o.order_id = 1
FOR UPDATE OF o.status WAIT 330. Захват строки в справочнике категорий
SELECT category_id,category_name
FROM categories
WHERE category_id = 50
FOR UPDATE WAIT 231. Ожидание строки по city_code
SELECT city_code,city_name
FROM city_dim
WHERE city_code = 'MSK'
FOR UPDATE WAIT 532. Захват строки до удаления вручную
SELECT doc_id
FROM docs
WHERE status = 'ARCHIVE'
AND doc_id = 15
FOR UPDATE WAIT 133. Блокировка рейтинга фильма перед пересчётом
SELECT movie_id,rating
FROM movie_scores
WHERE movie_id = 909
FOR UPDATE WAIT 734. Ожидание строки инвентаризации на складе
SELECT sku,wh_id,qty
FROM stock_locations
WHERE sku = 'X1'
AND wh_id = 2
FOR UPDATE WAIT 435. Захват строки customer_score
SELECT customer_id,score
FROM customer_score
WHERE customer_id = 808
FOR UPDATE WAIT 536. Блокировка строки отчёта по дню
SELECT rep_date,total_amt
FROM daily_totals
WHERE rep_date = DATE '2026-03-22'
FOR UPDATE WAIT 637. Захват промо-кампании перед продлением
SELECT campaign_id,end_at
FROM campaigns
WHERE campaign_id = 202
FOR UPDATE WAIT 338. Выбор нескольких строк уведомлений по статусу
SELECT msg_id,status
FROM notif_state
WHERE status = 'READY'
FOR UPDATE WAIT 539. Блокировка текущей цены перед ручной правкой
SELECT product_id,price
FROM current_price
WHERE product_id = 100
FOR UPDATE WAIT 440. Захват строки параметра приложения
SELECT param_code,param_value
FROM app_params
WHERE param_code = 'PAGE_SIZE'
FOR UPDATE WAIT 3Еще 20 примеров
41. UPDATE после ожидания в PL/SQL-блоке
DECLARE
v_status orders.status%TYPE;
BEGIN
SELECT status
INTO v_status
FROM orders
WHERE order_id = 1001
FOR UPDATE WAIT 5;
UPDATE orders
SET status = 'DONE'
WHERE order_id = 1001;
END;42. Ожидание строки клиента и изменение email
DECLARE
v_email users.email%TYPE;
BEGIN
SELECT email
INTO v_email
FROM users
WHERE user_id = 10
FOR UPDATE WAIT 4;
UPDATE users
SET email = LOWER(v_email)
WHERE user_id = 10;
END;43. Попытка захвата и обработка ошибки таймаута
BEGIN
BEGIN
SELECT qty
INTO :v_qty
FROM stock
WHERE sku = 'A100'
FOR UPDATE WAIT 2;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
END;44. Захват первой новой задачи в воркере
DECLARE
v_task_id tasks.task_id%TYPE;
BEGIN
SELECT task_id
INTO v_task_id
FROM tasks
WHERE status = 'NEW'
ORDER BY task_id
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 3;
UPDATE tasks
SET status = 'IN_PROGRESS'
WHERE task_id = v_task_id;
END;45. Ожидание и начисление бонуса пользователю
DECLARE
v_bonus bonus_accrual.bonus_amt%TYPE;
BEGIN
SELECT bonus_amt
INTO v_bonus
FROM bonus_accrual
WHERE user_id = 15
AND accrual_dt = DATE '2026-03-22'
FOR UPDATE WAIT 5;
UPDATE bonus_accrual
SET bonus_amt = v_bonus + 100
WHERE user_id = 15
AND accrual_dt = DATE '2026-03-22';
END;46. Захват счета и увеличение баланса
DECLARE
v_balance accounts.balance%TYPE;
BEGIN
SELECT balance
INTO v_balance
FROM accounts
WHERE account_id = 2020
FOR UPDATE WAIT 8;
UPDATE accounts
SET balance = v_balance + 500
WHERE account_id = 2020;
END;47. Ожидание строки и логирование времени обработки
BEGIN
SELECT payment_id
INTO :v_id
FROM payments
WHERE payment_id = 555
FOR UPDATE WAIT 6;
INSERT INTO audit_events(id,created_at)
VALUES (:v_id,SYSDATE);
END;48. Захват inventory row и уменьшение остатка
DECLARE
v_qty stock.qty%TYPE;
BEGIN
SELECT qty
INTO v_qty
FROM stock
WHERE sku = 'X1'
FOR UPDATE WAIT 4;
UPDATE stock
SET qty = v_qty - 1
WHERE sku = 'X1';
END;49. Захват и обновление счётчика usage_cnt
DECLARE
v_cnt promo_codes.used_cnt%TYPE;
BEGIN
SELECT used_cnt
INTO v_cnt
FROM promo_codes
WHERE promo_code = 'SALE10'
FOR UPDATE WAIT 3;
UPDATE promo_codes
SET used_cnt = v_cnt + 1
WHERE promo_code = 'SALE10';
END;50. Выбор строки с WAIT и последующий DELETE
BEGIN
SELECT msg_id
INTO :v_msg_id
FROM notif_queue
WHERE msg_id = 12345
FOR UPDATE WAIT 3;
DELETE FROM notif_queue
WHERE msg_id = :v_msg_id;
END;51. Захват строки маршрута и пересчёт ETA
DECLARE
v_eta route_eta.eta_min%TYPE;
BEGIN
SELECT eta_min
INTO v_eta
FROM route_eta
WHERE route_id = 5
FOR UPDATE WAIT 3;
UPDATE route_eta
SET eta_min = v_eta + 2
WHERE route_id = 5;
END;52. Ожидание строки отчёта и фиксация total_amt
BEGIN
SELECT total_amt
INTO :v_total
FROM daily_totals
WHERE rep_date = TRUNC(SYSDATE)
FOR UPDATE WAIT 5;
UPDATE daily_totals
SET total_amt = :v_total + 100
WHERE rep_date = TRUNC(SYSDATE);
END;53. Захват документа перед архивированием статуса
BEGIN
SELECT status
INTO :v_status
FROM docs
WHERE doc_id = 777
FOR UPDATE WAIT 10;
UPDATE docs
SET status = 'ARCHIVE'
WHERE doc_id = 777;
END;54. WAIT в курсоре FOR UPDATE
DECLARE
CURSOR c IS
SELECT task_id,status
FROM tasks
WHERE status = 'NEW'
FOR UPDATE WAIT 2;
BEGIN
FOR r IN c LOOP
UPDATE tasks
SET status = 'DONE'
WHERE CURRENT OF c;
END LOOP;
END;55. Курсор FOR UPDATE WAIT по таблице уведомлений
DECLARE
CURSOR c IS
SELECT msg_id,status
FROM notif_state
WHERE status = 'READY'
FOR UPDATE WAIT 4;
BEGIN
FOR r IN c LOOP
UPDATE notif_state
SET status = 'SENT'
WHERE CURRENT OF c;
END LOOP;
END;56. Явный OPEN cursor с WAIT
DECLARE
CURSOR c IS
SELECT order_id
FROM orders
WHERE status = 'NEW'
FOR UPDATE WAIT 3;
v_id orders.order_id%TYPE;
BEGIN
OPEN c;
LOOP
FETCH c INTO v_id;
EXIT WHEN c%NOTFOUND;
UPDATE orders
SET status = 'LOCKED'
WHERE CURRENT OF c;
END LOOP;
CLOSE c;
END;57. WAIT с OF для конкретной таблицы в join-курсорe
DECLARE
CURSOR c IS
SELECT o.order_id,o.status,u.email
FROM orders o
JOIN users u ON u.user_id = o.user_id
WHERE o.status = 'NEW'
FOR UPDATE OF o.status WAIT 3;
BEGIN
FOR r IN c LOOP
UPDATE orders
SET status = 'PROCESSING'
WHERE CURRENT OF c;
END LOOP;
END;58. Локальная обработка ошибки ORA-30006
BEGIN
BEGIN
SELECT customer_id
INTO :v_id
FROM customers
WHERE customer_id = 10
FOR UPDATE WAIT 1;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -30006 THEN
DBMS_OUTPUT.PUT_LINE('timeout');
ELSE
RAISE;
END IF;
END;
END;59. WAIT и повторная попытка через loop
DECLARE
v_try NUMBER := 0;
BEGIN
LOOP
v_try := v_try + 1;
BEGIN
SELECT order_id
INTO :v_id
FROM orders
WHERE order_id = 1001
FOR UPDATE WAIT 1;
EXIT;
EXCEPTION
WHEN OTHERS THEN
EXIT WHEN v_try = 3;
END;
END LOOP;
END;60. WAIT и COMMIT после успешного обновления
BEGIN
SELECT status
INTO :v_status
FROM orders
WHERE order_id = 1001
FOR UPDATE WAIT 5;
UPDATE orders
SET status = 'DONE'
WHERE order_id = 1001;
COMMIT;
END;Еще 20 примеров
61. Выбор между WAIT и NOWAIT в комментарии к стратегии
SELECT task_id,status
FROM tasks
WHERE task_id = 77
FOR UPDATE WAIT 562. WAIT для “мягкой” конкурентной обработки очереди
SELECT queue_id,queue_status
FROM job_queue
WHERE queue_status = 'NEW'
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 263. Ожидание и нормализация логина пользователя
BEGIN
SELECT login
INTO :v_login
FROM user_login
WHERE user_id = 42
FOR UPDATE WAIT 4;
UPDATE user_login
SET login = LOWER(TRIM(:v_login))
WHERE user_id = 42;
END;64. WAIT и корректировка суммы по sign-логике
BEGIN
SELECT amount
INTO :v_amount
FROM payment_sign
WHERE payment_id = 1
FOR UPDATE WAIT 3;
UPDATE payment_sign
SET sign_code = SIGN(:v_amount)
WHERE payment_id = 1;
END;65. Ожидание при upsert-подготовке в MERGE-сценарии
SELECT biz_key,attr_val
FROM sync_target
WHERE biz_key = 'A-100'
FOR UPDATE WAIT 566. WAIT в customer support для “одного оператора на клиента”
SELECT ticket_id,status
FROM tickets
WHERE customer_id = 15
AND status = 'OPEN'
FOR UPDATE WAIT 667. Блокировка профиля перед сменой плана подписки
SELECT sub_id,plan_code
FROM subscriptions
WHERE user_id = 15
FOR UPDATE WAIT 468. Ожидание при переводе средств между счетами
SELECT account_id,balance
FROM accounts
WHERE account_id IN (100,200)
FOR UPDATE WAIT 1069. Блокировка строки маршрута с OF eta_min
SELECT route_id,eta_min
FROM route_eta
WHERE route_id = 5
FOR UPDATE OF eta_min WAIT 370. Захват строки курса валют перед пересчётом цен
SELECT ccy,rate
FROM fx_rates
WHERE ccy = 'EUR'
AND rate_date = TRUNC(SYSDATE)
FOR UPDATE WAIT 571. WAIT и очистка архивного признака
BEGIN
SELECT status
INTO :v_status
FROM docs
WHERE doc_id = 15
FOR UPDATE WAIT 2;
UPDATE docs
SET status = 'ACTIVE'
WHERE doc_id = 15;
END;72. Ожидание перед ручной правкой tax rate
SELECT country_code,tax_rate
FROM tax_rates
WHERE country_code = 'DE'
FOR UPDATE WAIT 873. WAIT на строке search_top перед увеличением счётчика
BEGIN
SELECT cnt
INTO :v_cnt
FROM search_top
WHERE query_text = 'oracle sql'
FOR UPDATE WAIT 3;
UPDATE search_top
SET cnt = :v_cnt + 1
WHERE query_text = 'oracle sql';
END;74. Блокировка daily snapshot по дате запуска
SELECT run_dt,run_status
FROM run_log
WHERE run_dt = TRUNC(SYSDATE)
FOR UPDATE WAIT 575. WAIT при правке вручную app_params
SELECT param_code,param_value
FROM app_params
WHERE param_code = 'THEME'
FOR UPDATE WAIT 276. Блокировка city_dim перед коррекцией названия
SELECT city_code,city_name
FROM city_dim
WHERE country_code = 'RU'
AND city_code = 'MSK'
FOR UPDATE WAIT 477. WAIT в отчётной витрине KPI
SELECT kpi_code,plan_val,fact_val
FROM kpi_table
WHERE kpi_code = 'REV'
FOR UPDATE WAIT 378. Захват строки geo_key
SELECT geo_key
FROM geo_key_demo
WHERE geo_key = 'RU:MSK'
FOR UPDATE WAIT 179. WAIT на blacklist для безопасной модификации причины
SELECT email,reason
FROM blacklist
WHERE email = 'spam@example.com'
FOR UPDATE WAIT 580. Блокировка file_report перед перезаписью статистики
SELECT file_name,row_cnt
FROM file_report
WHERE file_name = 'load_20260322.csv'
FOR UPDATE WAIT 4Еще 20 примеров
81. WAIT с подзапросом в WHERE
SELECT order_id,status
FROM orders
WHERE user_id IN (
SELECT user_id
FROM users
WHERE email LIKE '%@example.com'
)
FOR UPDATE WAIT 382. WAIT и фильтр по агрегированному признаку через EXISTS
SELECT c.customer_id,c.customer_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = c.customer_id
)
FOR UPDATE WAIT 483. WAIT на первой строке по ORDER BY и OFFSET
SELECT msg_id,status
FROM notif_queue
ORDER BY msg_id
OFFSET 0 ROWS
FETCH NEXT 1 ROW ONLY
FOR UPDATE WAIT 284. Захват строки, найденной по regexp-нормализации телефона
SELECT contact_id,phone
FROM contact_book
WHERE REGEXP_REPLACE(phone,'\D') = '15551234567'
FOR UPDATE WAIT 585. WAIT в merge_key-ориентированном поиске
SELECT merge_key
FROM merge_key_demo
WHERE merge_key = 'crm:100'
FOR UPDATE WAIT 386. Блокировка строки по JSON-извлечённому идентификатору
SELECT cache_key,cache_val
FROM api_cache
WHERE cache_key = '100'
FOR UPDATE WAIT 487. WAIT с текущей датой в run_log
SELECT run_status
FROM run_log
WHERE run_dt = TRUNC(SYSDATE)
FOR UPDATE WAIT 188. Захват строки email_list перед деактивацией
SELECT email
FROM email_list
WHERE email = 'demo@example.com'
FOR UPDATE WAIT 389. WAIT при пакетной ручной корректировке subscriptions
SELECT sub_id,plan_code
FROM subscriptions
WHERE status = 'ACTIVE'
AND user_id = 500
FOR UPDATE WAIT 590. Ожидание строк склада по одному bucket
SELECT sku,qty
FROM stock
WHERE MOD(product_id,16) = 3
FOR UPDATE WAIT 291. WAIT для ручной коррекции score
SELECT customer_id,score
FROM customer_score
WHERE score > 900
FOR UPDATE WAIT 292. Захват одной строки route_eta по минимальному route_id
SELECT route_id,eta_min
FROM route_eta
ORDER BY route_id
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 393. WAIT с OF и join по blacklist
SELECT b.email,b.reason,u.user_id
FROM blacklist b
LEFT JOIN users u ON LOWER(u.email) = LOWER(b.email)
WHERE b.email = 'spam@example.com'
FOR UPDATE OF b.reason WAIT 494. Захват строки phones перед сменой номера
SELECT user_id,phone
FROM phones
WHERE user_id = 77
FOR UPDATE WAIT 595. WAIT на monthly summary перед закрытием периода
SELECT ym_key,total_amt
FROM month_totals
WHERE ym_key = TO_NUMBER(TO_CHAR(SYSDATE,'YYYYMM'))
FOR UPDATE WAIT 1096. Захват строки campaign и обновление end_at в PL/SQL
BEGIN
SELECT end_at
INTO :v_end_at
FROM campaigns
WHERE campaign_id = 202
FOR UPDATE WAIT 3;
UPDATE campaigns
SET end_at = :v_end_at + 7
WHERE campaign_id = 202;
END;97. WAIT на строке active_users для деактивации
SELECT user_id,is_active
FROM active_users
WHERE user_id = 15
FOR UPDATE WAIT 498. Захват строки stock_locations перед переносом товара
SELECT sku,wh_id,loc_code
FROM stock_locations
WHERE sku = 'BOX-1'
AND wh_id = 10
FOR UPDATE WAIT 699. WAIT на конкретной строке docs по hash
SELECT doc_id,status
FROM docs
WHERE payload_hash = STANDARD_HASH('payload','SHA256')
FOR UPDATE WAIT 2100. Финальный пример: выбор строки очереди, ожидание, обновление статуса и commit
DECLARE
v_task_id tasks.task_id%TYPE;
BEGIN
SELECT task_id
INTO v_task_id
FROM tasks
WHERE status = 'NEW'
ORDER BY task_id
FETCH FIRST 1 ROW ONLY
FOR UPDATE WAIT 5;
UPDATE tasks
SET status = 'DONE'
WHERE task_id = v_task_id;
COMMIT;
END;Заключение
WAIT в Oracle — это не про “тормозить”, а про грамотную стратегию конкурентного доступа.
Иногда системе действительно лучше немного подождать, чем сразу падать с ошибкой.
Именно тут начинается красивая инженерия: ты уже не просто пишешь запросы, а настраиваешь поведение базы в живой многосессионной среде.
Если любишь SQL за контроль и предсказуемость — такие конструкции особенно приятно держать в арсенале.
Официальная документация Oracle:
https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html
🔜 Следующая статья:
VIRTUAL в Oracle SQL — как создавать вычисляемые столбцы без хранения данных