SELECT ... FOR UPDATE читає рядки й блокує їх до кінця транзакції. Інші транзакції, які захочуть змінити ці рядки чи теж взяти FOR UPDATE, чекатимуть.
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- тепер ніхто не змінить баланс, поки ми рахуємо
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
Так закривають гонку «прочитав → перевірив → записав»: друга транзакція прочитає баланс лише після того, як перша закомітить.
FOR UPDATE SKIP LOCKED (PostgreSQL 9.5+, MySQL 8.0+) - не чекати, а пропустити вже заблоковані рядки. Ідеально для черги завдань у таблиці: кілька воркерів беруть різні завдання без конфліктів.
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;
Саме так працює драйвер черги database у Laravel.
NOWAIT - не чекати, а одразу отримати помилку, якщо рядок зайнятий.
Пастки:
- Блокування тримається до кінця транзакції. Без
BEGIN(в autocommit) воно знімається одразу після запиту й нічого не захищає. - Довга транзакція з
FOR UPDATE- це черга з інших запитів. Усередині не роблять HTTP-запитів чи інших повільних дій. - Блокування рядків у різному порядку в різних транзакціях - прямий шлях до deadlock.