Работа с несколькими связанными таблицами в одном сценарии должна быть атомарной. Без
TRANSACTION при автокоммите каждый UPDATE фиксируется отдельно — и есть риск получить несогласованное состояние.Таблицы:
wallet(user_id, balance)
payments(id, user_id, amount, status)
Сценарий: списать баланс и перевести платёж в
done.Если соединение обрывается между запросами:
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
-- сбой соединения тут
UPDATE payments SET status = 'done' WHERE id = 55;
Первое изменение уже сохранено, второе — нет. Итог: деньги списаны, платёж не подтверждён.
Правильный подход — транзакция:
BEGIN;
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
UPDATE payments SET status = 'done' WHERE id = 55;
COMMIT;
До
COMMIT изменения видны только в текущей сессии и не становятся устойчивыми. Если все шаги успешны — фиксируется сразу всё.Откат при ошибке:
BEGIN;
UPDATE wallet SET balance = balance - 100 WHERE user_id = 7;
UPDATE payments SET status = 'done' WHERE id = 55;
ROLLBACK;
Пока сессия жива, открытая транзакция не откатывается сама по себе и может удерживать блокировки. Автоматический
ROLLBACK происходит именно при разрыве соединения или явном откате.Нюанс для защиты от гонок и повторной обработки:
UPDATE wallet
SET balance = balance - 100
WHERE user_id = 7 AND balance >= 100;
UPDATE payments
SET status = 'done'
WHERE id = 55 AND status = 'pending';
Оба
UPDATE выполняем в одной транзакции, затем проверяем rowcount: если любой запрос затронул 0 строк — ROLLBACK и обработка как ошибка.🔥
SELECT ... FOR UPDATE также работает только в той же транзакции, где будет обновление. Любая операция списания + фиксации платежа = транзакция.➡️ SQL Ready | #практика