Транзакции
Некоторые изменения неразделимы. Новый заказ — это строка в orders и строка в order_items для каждой его игры. А что, если заказ попал в базу, а потом что-то пошло не так, прежде чем туда попали его позиции? Неверный ID игры, сбой приложения, на сервере пропало питание. Тогда в магазине окажется заказ, в котором ничего нет. Такие изменения должны происходить либо все, либо никакие.
Для этого и нужна транзакция. BEGIN начинает её, и изменения после него входят в неё. COMMIT завершает её и сохраняет все её изменения разом. Заказ Max Baker на Goal Rush, оформленный безопасно:
BEGIN;
INSERT INTO orders (customer_id, ordered_at, status)
VALUES (14, '2026-01-08', 'paid');
INSERT INTO order_items (order_id, game_id, quantity, price)
VALUES (31, 8, 1, 50);
COMMIT;До COMMIT никто другой не видит изменений: остальные пользователи базы данных видят магазин таким, каким он был до BEGIN. После него они видят все изменения сразу, а не половину. А если посреди что-то пошло не так, приложение вместо этого завершает транзакцию командой ROLLBACK, которая отменяет все изменения с момента BEGIN, как будто их и не было. Наполовину оформленный заказ исчезает.
Внутри транзакции вы видите собственные изменения, поэтому её можно использовать и чтобы что-то попробовать, посмотреть на результат и всё вернуть:
BEGIN;
DELETE FROM reviews;
SELECT COUNT(*) AS reviews
FROM reviews;
ROLLBACK;
SELECT COUNT(*) AS reviews
FROM reviews;| reviews |
|---|
| 0 |
| reviews |
|---|
| 25 |
Первый запрос не находит отзывов, второй снова находит все 25: ROLLBACK их вернул.
Без BEGIN каждая инструкция — это маленькая транзакция сама по себе. Поэтому UPDATE, который падает посередине, ничего не меняет, как вы видели с ценами. Транзакция делает то же самое для нескольких инструкций вместе. (В других базах данных вместо BEGIN можно увидеть START TRANSACTION.)
В этом курсе каждый запуск начинается со свежей базы данных, поэтому разницы, которую даёт COMMIT, вы не увидите, но в настоящей базе именно он делает изменения постоянными. Типичная ошибка: забыть COMMIT. Ваши собственные запросы видят изменения, и всё выглядит нормально, но когда приложение отключается, база данных откатывает незавершённую транзакцию, и изменения теряются. Здесь вы заметите это при следующем BEGIN: одна транзакция не может начаться внутри другой, поэтому он заканчивается ошибкой cannot start a transaction within a transaction. И COMMIT без BEGIN — тоже ошибка: cannot commit - no transaction is active.
Задание
- 1
Henry Scott (клиент 12) делает свой первый заказ, 2026-01-15: один экземпляр Star Pilots (игра 7) и один Cube Logic (игра 6), каждый по цене из
games. В одной транзакции добавьте заказ со статусомpaid(его ID придёт сам, это будет 31) и две его позиции, а затем зафиксируйте транзакцию. - 2
Ниже покажите, что входит в заказ 31: название каждой игры, количество и цену, отсортировав их по названию:
title quantity price Cube Logic 1 5 Star Pilots 1 35 - 3
Henry хочет заказать ещё и Robot Rebellion. Начните новую транзакцию, добавьте его второй заказ (от 2026-01-16, статус
paid) и покажите ID и дату всех заказов Henry, отсортировав их по ID. Но Robot Rebellion ещё не вышла, поэтому магазин не может её продать: откатите транзакцию и покажите его заказы ещё раз тем же запросом. Два результата должны быть такими:id ordered_at 31 2026-01-15 32 2026-01-16 id ordered_at 31 2026-01-15