Что такое транзакция
Транзакция — логически неделимая единица работы с базой данных, последовательность операций (INSERT, UPDATE, DELETE), выполняемая как единое целое («всё или ничего»). Жизненный цикл: BEGIN (начало) → операции → COMMIT (фиксация, изменения становятся постоянными) или ROLLBACK (откат, все изменения отменяются). SAVEPOINT — точки сохранения для частичного отката. Автокоммит (autocommit) — каждая команда как отдельная транзакция (опасно для многошаговых операций). Пример: банковский перевод (два UPDATE в одной транзакции). Восстановление после сбоя: REDO (повторное применение зафиксированных транзакций) и UNDO (откат незавершённых) через WAL. OLTP (короткие транзакции, тысячи в секунду) vs OLAP (длинные транзакции, агрегации). Распределённые транзакции (2PC — Two-Phase Commit) для нескольких баз данных (сложны, медленны). Транзакции не должны включать внешние вызовы (API, email, ожидание пользователя).
Введение: Ошибка в банкомате
Представьте, что вы переводите деньги с карты на карту через банкомат. Вы вводите сумму, номер счета получателя, нажимаете “Перевести”. Банкомат сообщает: “Операция выполнена”. Вы смотрите на чек — деньги списаны с вашего счета. Но получатель говорит, что деньги не пришли.
Что произошло? Банкомат выполнил только первую часть операции: списал деньги с вашего счета. А вторая часть — зачислить деньги получателю — по какой-то причине не выполнилась. Сбой сети, ошибка в программе, отключение электричества.
В результате деньги потеряны. Ваши — да. Банковские — нет. Система оказалась в противоречивом состоянии: деньги ушли из одного места, но не пришли в другое.
Транзакция — это механизм, который предотвращает такие ситуации. Транзакция гарантирует, что группа операций выполнится либо полностью, либо не выполнится вообще. Нет промежуточных состояний, когда часть операций уже сделана, а часть еще нет.
Если перевод денег оформлен как транзакция, то в случае сбоя система автоматически откатит (отменит) списание денег с вашего счета. Ваши деньги останутся на месте, а вы получите сообщение об ошибке. Лучше потерять возможность перевода, чем потерять деньги.
Транзакция: Определение
Транзакция — это логически неделимая единица работы с базой данных. Она представляет собой последовательность операций (чтение, вставка, обновление, удаление), которые выполняются как единое целое.
Транзакция обладает двумя ключевыми свойствами, которые отличают ее от простой последовательности запросов:
Неделимость (атомарность): Транзакция либо выполняется полностью, либо не выполняется вообще. Не может быть ситуации, когда часть операций применена, а часть — нет.
Изолированность (частично): Пока транзакция не завершена, ее промежуточные результаты не видны другим транзакциям. Каждая транзакция работает так, как будто она одна в системе.
Остальные свойства транзакций (согласованность, долговечность) относятся к теме ACID и будут рассмотрены отдельно. Здесь мы фокусируемся на самом понятии транзакции и механизмах ее выполнения.
Простыми словами: Транзакция — это “все или ничего”. Либо все операции внутри транзакции успешно применяются к базе данных, либо ни одна из них не применяется. База данных никогда не оказывается в “половинчатом” состоянии.
Жизненный цикл транзакции
Транзакция проходит несколько четко определенных этапов от начала до конца.
Начало транзакции
Транзакция начинается явно или неявно.
- Явное начало: Вы пишете
BEGIN TRANSACTION,START TRANSACTIONилиBEGIN(в зависимости от СУБД). - Неявное начало: В некоторых СУБД каждая отдельная команда SQL автоматически выполняется как отдельная транзакция (режим автокоммита). В других СУБД транзакция начинается с первой команды после завершения предыдущей транзакции.
-- Явное начало транзакции (синтаксис зависит от СУБД)
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- Завершение транзакции
COMMIT;Выполнение операций
Внутри транзакции вы можете выполнять любые операции с базой данных: SELECT, INSERT, UPDATE, DELETE. Все эти операции временно видны только внутри текущей транзакции (в зависимости от уровня изоляции, но об этом позже в отдельной теме).
Важно понимать: пока транзакция не завершена, изменения не считаются постоянными. Они существуют только в контексте этой транзакции. Другие транзакции могут их видеть или не видеть — это зависит от настроек изоляции.
Фиксация (COMMIT)
COMMIT — это команда, которая делает все изменения транзакции постоянными. После COMMIT:
- Изменения записываются на диск (или в журнал предзаписи – WAL).
- Другие транзакции начинают видеть эти изменения (в зависимости от уровня изоляции).
- Транзакция завершается, и ее ресурсы освобождаются.
COMMIT; -- или COMMIT TRANSACTION, COMMIT WORKОткат (ROLLBACK)
ROLLBACK — это команда, которая отменяет все изменения, сделанные в текущей транзакции. После ROLLBACK:
- База данных возвращается к состоянию, которое было на момент начала транзакции.
- Никакие изменения не применяются.
- Транзакция завершается, и ее ресурсы освобождаются.
ROLLBACK; -- или ROLLBACK TRANSACTION, ROLLBACK WORKROLLBACK может быть выполнен:
- Явно — программист написал
ROLLBACKв коде (например, при обнаружении ошибки). - Автоматически — СУБД выполняет откат при возникновении ошибки внутри транзакции (например, нарушение уникальности, деление на ноль, сбой соединения).
- Принудительно — администратор или система могут прервать “висячую” транзакцию.
Точка сохранения (SAVEPOINT)
Внутри транзакции можно создавать промежуточные точки сохранения, чтобы откатываться не до начала транзакции, а только до определенной точки.
BEGIN TRANSACTION;
INSERT INTO logs (message) VALUES ('Шаг 1 выполнен');
SAVEPOINT step1;
INSERT INTO logs (message) VALUES ('Шаг 2 выполнен');
-- Ошибка! Откатываемся только до step1
ROLLBACK TO SAVEPOINT step1;
INSERT INTO logs (message) VALUES ('Шаг 2 (повторный)');
COMMIT;Точки сохранения полезны в сложных сценариях, когда нужно обрабатывать ошибки частично, не теряя весь прогресс транзакции.
Транзакции в реальной жизни: Банковский перевод
Рассмотрим классический пример — перевод денег между счетами.
Без транзакции:
-- Шаг 1: Списать 100 рублей со счета 1
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Шаг 2: Зачислить 100 рублей на счет 2
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;Что произойдет, если между шагом 1 и шагом 2 произойдет сбой (отключение электричества, обрыв сети, ошибка в коде)?
- Деньги списаны со счета 1.
- Деньги не зачислены на счет 2.
- Деньги исчезли из системы. Банк в убытке. Клиент в ярости.
С транзакцией:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;Если сбой произойдет до COMMIT, СУБД автоматически выполнит ROLLBACK. Деньги не будут списаны ни с одного счета. Система останется в согласованном состоянии.
Что происходит под капотом:
- При
BEGIN TRANSACTIONСУБД выделяет ресурсы для транзакции. - При
UPDATEСУБД записывает изменения в специальную область (журнал транзакций, буферные страницы), но не применяет их постоянно. - При
COMMITСУБД записывает информацию о фиксации в журнал (WAL) и затем применяет изменения к основному хранилищу данных. - При сбое до
COMMITСУБД при восстановлении увидит незавершенную транзакцию в журнале и выполнит ее откат.
Транзакции в разных сценариях
OLTP (Online Transaction Processing)
Классические OLTP-системы — это банковские системы, системы бронирования билетов, интернет-магазины. Для них транзакции:
- Короткие (миллисекунды или доли секунды).
- Манипулируют небольшим количеством строк.
- Требуют высокой степени изоляции (чтобы не было конфликтов).
- Обрабатывают тысячи или миллионы транзакций в секунду.
Пример: Оформление заказа в интернет-магазине — одна транзакция, которая проверяет остаток товара, резервирует его, списывает деньги, создает заказ.
OLAP (Online Analytical Processing)
Аналитические системы (хранилища данных, отчеты) используют транзакции иначе:
- Длинные транзакции (минуты или часы).
- Манипулируют миллионами строк.
- Часто используют более слабые уровни изоляции ради производительности.
- Транзакций меньше, но каждая обрабатывает огромные объемы.
Пример: Пересчет итогов продаж за месяц — одна транзакция, которая агрегирует миллионы записей.
Распределенные транзакции (XA-транзакции)
Когда одна транзакция затрагивает несколько независимых баз данных (или других ресурсов), нужны распределенные транзакции. Протокол двухфазной фиксации (2PC — Two-Phase Commit) обеспечивает атомарность на нескольких узлах.
Фаза 1 (prepare): Каждый участник сообщает координатору: “Я готов зафиксировать изменения”.
Фаза 2 (commit/rollback): Если все участники ответили “готов”, координатор говорит “фиксируй”. Если кто-то ответил “нет” или не ответил — “откатывай”.
sequenceDiagram
participant Coordinator
participant DB1
participant DB2
Coordinator->>DB1: Prepare
Coordinator->>DB2: Prepare
DB1-->>Coordinator: Ready
DB2-->>Coordinator: Ready
Coordinator->>DB1: Commit
Coordinator->>DB2: Commit
Распределенные транзакции сложны, медленны и чувствительны к сбоям сети. Современные микросервисные архитектуры часто избегают их в пользу саг (Saga) и событийно-ориентированных подходов.
Автокоммит (Autocommit)
В большинстве СУБД по умолчанию включен режим автокоммита. Это означает, что каждая отдельная SQL-команда автоматически выполняется как собственная транзакция: неявный BEGIN перед командой и неявный COMMIT после успешного выполнения.
-- В режиме автокоммита это:
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Эквивалентно этому:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;Почему автокоммит — это проблема для сложных операций:
Если вам нужно выполнить две операции как одну транзакцию, автокоммит помешает. Каждая операция зафиксируется отдельно.
-- В режиме автокоммита это НЕ транзакция:
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- При сбое между ними — деньги потеряныКак отключить автокоммит:
-- В PostgreSQL, MySQL, SQL Server обычно:
SET autocommit = 0; -- или OFF
-- После этого нужно явно писать COMMIT или ROLLBACKВ некоторых СУБД (например, PostgreSQL) автокоммит отключается неявно при первом BEGIN.
Вложенные транзакции
Некоторые СУБД поддерживают вложенные транзакции — транзакции внутри транзакций.
BEGIN TRANSACTION; -- Внешняя транзакция
INSERT INTO logs VALUES ('Начали');
BEGIN TRANSACTION; -- Вложенная транзакция
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- Фиксация вложенной транзакции
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
COMMIT; -- Фиксация внешней транзакцииВажное предостережение: В большинстве реализаций вложенные транзакции — это на самом деле точки сохранения (savepoints) под капотом. Внутренний COMMIT не делает изменения постоянными — они станут постоянными только после COMMIT внешней транзакции. Внутренний ROLLBACK откатывается до соответствующей точки сохранения.
BEGIN TRANSACTION; -- Транзакция T1
INSERT INTO t1 VALUES (1);
SAVEPOINT sp1;
INSERT INTO t1 VALUES (2);
ROLLBACK TO SAVEPOINT sp1; -- Значение 2 не вставится
INSERT INTO t1 VALUES (3);
COMMIT; -- Вставятся значения 1 и 3Не стоит полагаться на вложенные транзакции как на настоящую изоляцию уровней. Это скорее синтаксический сахар для точек сохранения.
Синтаксический сахар – это синтаксические возможности, которые не влияют на поведение программы, но делают использование языка более удобным для человека.
Что происходит при сбое: Механизм восстановления
Когда СУБД запускается после сбоя (падение сервера, отключение электричества, ошибка операционной системы), она должна восстановить согласованное состояние. Для этого используется журнал предзаписи (WAL — Write-Ahead Logging), который будет подробно рассмотрен в отдельном документе.
Как работает восстановление после сбоя:
СУБД читает журнал транзакций с диска. Журнал содержит все изменения, которые произошли до сбоя, с отметками о том, какие транзакции зафиксированы, а какие — нет.
Redo (повторное применение): Все изменения зафиксированных транзакций, которые не были записаны на диск до сбоя, применяются снова. Это гарантирует, что зафиксированные данные не потеряны.
Undo (откат): Все изменения незавершенных транзакций отменяются. База данных возвращается к состоянию, которое было до начала этих транзакций.
Результат: После восстановления база данных оказывается в согласованном состоянии. Зафиксированные транзакции сохранены. Незафиксированные — отменены. Никаких “половинчатых” состояний.
Резюме для системного аналитика
Транзакция — это механизм “все или ничего”. Она гарантирует, что группа операций выполнится полностью или не выполнится вообще. Без транзакций базы данных быстро приходят в противоречивое состояние.
Основные команды:
BEGIN TRANSACTION(начало),COMMIT(фиксация),ROLLBACK(откат). Точки сохранения (SAVEPOINT) позволяют откатываться частично.Автокоммит — враг сложных операций. В режиме автокоммита каждая команда фиксируется отдельно. Для многошаговых операций автокоммит нужно отключать.
Короткие транзакции — хорошие транзакции. Чем короче транзакция, тем меньше конфликтов, блокировок и проблем с производительностью.
Транзакции не должны включать внешние вызовы. API, email, ожидание пользователя — все это должно быть за пределами транзакции.
Распределенные транзакции (2PC) — сложный инструмент. В современных распределенных системах их часто заменяют сагами и событийной архитектурой.