В прошлом уроке каждая команда делала одно дело и сразу фиксировалась. Оформление заказа так не работает: это несколько команд подряд, и выполниться они должны вместе. Что такое транзакция и четыре буквы ACID, мы уже разобрали в уроке теории. Здесь смотрим, как транзакции ведут себя в живом PostgreSQL: где они начинаются сами, что бывает после ошибки и как откатить только часть работы. Примеры работают на учебной базе магазина.
Корзина на кассе
Покупатель выкладывает на ленту три книги. Кассир пробивает их одну за другой, но чек существует только целиком: если на третьей книге карта не прошла, кассир отменяет весь чек, а не отдаёт две книги из трёх. Пока чек не закрыт, всё пробитое можно отменить.
Транзакция это такой чек. BEGIN открывает его, COMMIT закрывает и делает изменения постоянными, ROLLBACK отменяет всё пробитое.
BEGIN;
INSERT INTO orders (id, customer_id, created_at, status)
VALUES (6, 4, '2026-10-09', 'new');
INSERT INTO order_items (order_id, book_id, qty, price)
VALUES (6, 7, 1, 1290.00);
COMMIT;
Светлана наконец сделала первый заказ. Заказ и его позиция появились вместе: никто не видел заказ без позиций.
Автофиксация
А что происходит без BEGIN? Каждая команда становится отдельной транзакцией и фиксируется сразу после выполнения. Этот режим называют автофиксацией, и он включён по умолчанию в psql, в GUI-клиентах и в драйверах для языков программирования.
Отсюда частая ошибка: две команды заказа без BEGIN это две отдельные транзакции. Если приложение упадёт между ними, в базе останется заказ без позиций.
В psql автофиксацию можно выключить командой \set AUTOCOMMIT off. Тогда psql сам ставит невидимый BEGIN перед первой командой, и ничего не фиксируется, пока ты не напишешь COMMIT. На рабочей базе, где цена случайного UPDATE высока, это страхует от ошибок.
Ошибка внутри транзакции
Попробуем оформить ещё один заказ, но ошибёмся в номере книги:
BEGIN;
INSERT INTO orders (id, customer_id, created_at, status)
VALUES (7, 4, '2026-10-09', 'new');
INSERT INTO order_items (order_id, book_id, qty, price)
VALUES (7, 99, 1, 500.00);
ERROR: insert or update on table "order_items" violates foreign key constraint "order_items_book_id_fkey"
DETAIL: Key (book_id)=(99) is not present in table "books".
Книги 99 нет. Исправляем и пробуем вставить правильную позицию:
INSERT INTO order_items (order_id, book_id, qty, price)
VALUES (7, 8, 1, 2490.00);
ERROR: current transaction is aborted, commands ignored until end of transaction block
Это поведение PostgreSQL удивляет тех, кто работал с другими СУБД. После любой ошибки транзакция становится прерванной: сервер отказывается выполнять её дальнейшие команды. Даже COMMIT здесь не зафиксирует ничего:
COMMIT;
ROLLBACK
psql написал ROLLBACK, а не COMMIT. Заказ 7 тоже исчез, хотя его INSERT прошёл успешно: транзакция откатилась целиком. Это атомарность в чистом виде. PostgreSQL не даёт зафиксировать половину работы, в которой что-то пошло не так.
урок из курса
Урок из курса «PostgreSQL с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.