Понимание транзакций баз данных
Когда пользователь размещает заказ на вашей платформе, происходит несколько событий одновременно: уменьшается количество товара на складе, создаётся запись о платеже и сохраняется запись о заказе. Если сервер упадёт на полпути, вы можете получить ситуацию, когда с клиента списаны деньги, но заказ не создан. Транзакции баз данных существуют именно для того, чтобы предотвратить подобные частичные сбои.
В этой статье рассматривается, что такое транзакции баз данных, как свойства ACID обеспечивают надёжность, как уровни изоляции транзакций контролируют конкурентный доступ, и как современные базы данных используют MVCC для эффективной реализации всего этого.
Ключевые выводы
- Транзакция базы данных группирует несколько операций в единую единицу работы — либо все операции выполняются успешно, либо все откатываются.
- Свойства ACID (Atomicity, Consistency, Isolation, Durability) определяют гарантии надёжности, которые обеспечивают транзакции.
- SQL определяет четыре уровня изоляции, но реальное поведение существенно различается в разных движках баз данных, таких как PostgreSQL, MySQL и SQLite.
- MVCC позволяет современным базам данных эффективно обрабатывать конкурентные чтения и записи, поддерживая несколько версий строк вместо использования тяжёлых блокировок.
Что такое транзакция базы данных?
Транзакция базы данных — это последовательность операций, рассматриваемая как единая единица работы. Либо все операции выполняются успешно и изменения фиксируются (commit), либо что-то не срабатывает и всё откатывается (rollback) к предыдущему состоянию.
В SQL базовая транзакция выглядит так:
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
Если что-либо не срабатывает между BEGIN и COMMIT, команда ROLLBACK восстанавливает базу данных в состояние до начала транзакции. Большинство баз данных также поддерживают SAVEPOINT для частичных откатов внутри транзакции.
Свойства ACID: что они на самом деле означают
ACID — это набор гарантий, которые делают транзакции надёжными:
- Atomicity (Атомарность) — Все шаги выполняются успешно, или ни один не выполняется. Никаких частичных обновлений.
- Consistency (Согласованность) — База данных переходит из одного валидного состояния в другое. Ограничения и правила соблюдаются.
- Isolation (Изоляция) — Конкурентные транзакции не мешают друг другу.
- Durability (Долговечность) — После фиксации изменения переживают сбои. Обычно это реализуется через журналирование с упреждающей записью (WAL — Write-Ahead Logging).
Соответствие ACID имеет свою цену с точки зрения производительности. Некоторые системы позволяют обменять строгие гарантии на скорость, поэтому понимание этих свойств важно при проектировании уровня данных.
Объяснение уровней изоляции транзакций
Изоляция — это наиболее сложное из четырёх свойств ACID. SQL определяет четыре стандартных уровня изоляции транзакций, каждый из которых допускает различные компромиссы между согласованностью и конкурентностью:
| Уровень изоляции | Грязное чтение | Неповторяющееся чтение | Фантомное чтение |
|---|---|---|---|
| READ UNCOMMITTED | Возможно | Возможно | Возможно |
| READ COMMITTED | Предотвращено | Возможно | Возможно |
| REPEATABLE READ | Предотвращено | Предотвращено | Возможно |
| SERIALIZABLE | Предотвращено | Предотвращено | Предотвращено |
Важно: Реальное поведение различается в зависимости от движка базы данных.
- PostgreSQL не реализует
READ UNCOMMITTED— он молча повышает его доREAD COMMITTED. По умолчанию используетсяREAD COMMITTED, как описано в официальном руководстве по уровням изоляции. - MySQL (InnoDB) по умолчанию использует
REPEATABLE READ. В определённых случаях используются gap locks для уменьшения фантомных чтений, но поведение зависит от уровня изоляции и паттерна запросов, как описано в документации по изоляции InnoDB. - SQLite использует модель с единственным писателем, что означает, что только одна транзакция записи может выполняться одновременно. Он поддерживает режимы транзакций
DEFERRED,IMMEDIATEиEXCLUSIVE, а не стандартные уровни изоляции, как описано в документации по транзакциям SQLite.
Не предполагайте идентичного поведения в разных базах данных. Всегда проверяйте документацию для вашего конкретного движка.
Discover how at OpenReplay.com.
Как MVCC делает изоляцию практичной
Большинство современных реляционных баз данных — включая PostgreSQL и MySQL (InnoDB) — реализуют изоляцию через многоверсионное управление конкурентностью (MVCC — Multi-Version Concurrency Control), а не через простые блокировки.
С MVCC запросы видят согласованный снимок данных, но то, делается ли этот снимок для каждого оператора или для каждой транзакции, зависит от уровня изоляции и движка базы данных. Операции записи создают новые версии строк, а не перезаписывают их. Читатели не блокируют писателей, и писатели не блокируют читателей.
Именно поэтому PostgreSQL может обслуживать нагрузки с интенсивным чтением без постоянного ожидания читателями блокировок. Компромисс заключается в том, что старые версии строк накапливаются и должны очищаться — в PostgreSQL это обрабатывается процессом VACUUM.
MVCC — это то, что делает REPEATABLE READ и изоляцию снимков практичными в масштабе.
Транзакции в разных системах
Не все базы данных обрабатывают транзакции одинаково:
- MySQL (InnoDB) поддерживает полные ACID-транзакции. Старый движок MyISAM не поддерживает.
- PostgreSQL имеет надёжный MVCC и поддерживает изоляцию
SERIALIZABLEс настоящими гарантиями сериализуемости. - SQLite полностью соответствует ACID, но сериализует все записи, что делает его непригодным для высококонкурентных нагрузок на запись.
- MongoDB добавила многодокументные транзакции в версии 4.0, но они несут больше накладных расходов, чем в традиционных реляционных базах данных, и лучше использовать их выборочно.
Практические рекомендации по использованию транзакций
- Делайте транзакции короткими. Долго выполняющиеся транзакции удерживают блокировки или сохраняют старые снимки MVCC, что ухудшает производительность.
- Не оборачивайте запросы только на чтение в транзакции без необходимости — особенно в высоконагруженных API.
- Обрабатывайте ошибки явно. Всегда убедитесь, что
ROLLBACKпроисходит при сбое, независимо от того, пишете ли вы чистый SQL или используете ORM. - Выбирайте уровень изоляции осознанно.
READ COMMITTED— разумное значение по умолчанию для большинства веб-приложений. ИспользуйтеSERIALIZABLEтолько когда корректность этого требует, и тестируйте влияние на производительность.
Заключение
Транзакции баз данных дают вам надёжный способ поддерживать согласованность данных в нескольких операциях. Понимание свойств ACID говорит вам, с какими гарантиями вы работаете. Знание ваших уровней изоляции и того, как ваша конкретная база данных реализует MVCC, говорит вам, во что эти гарантии на самом деле обходятся — и где находятся граничные случаи. Это знание отличает разработчиков, которые используют транзакции, от разработчиков, которые используют их хорошо.
Часто задаваемые вопросы
Используйте SERIALIZABLE, когда ваше приложение требует строгих гарантий корректности, таких как финансовые расчёты или управление запасами, где конкурентные транзакции не должны производить конфликтующие результаты. Для большинства веб-приложений READ COMMITTED предлагает хороший баланс между согласованностью и производительностью. Всегда проводите бенчмарки SERIALIZABLE в вашей среде, так как это может снизить конкурентность и привести к повторным попыткам транзакций в зависимости от движка базы данных.
Большинство баз данных автоматически оборачивают каждый отдельный SQL-оператор в неявную транзакцию, поэтому одиночный INSERT или UPDATE уже атомарен. Явные транзакции становятся необходимыми, когда вам нужно сгруппировать несколько операторов в одну единицу работы, гарантируя, что они все выполнятся успешно или все откатятся вместе. Оборачивание одиночного оператора в BEGIN и COMMIT добавляет накладные расходы без существенной пользы.
Всегда используйте блоки try-catch или эквивалентную обработку ошибок вокруг вашей логики транзакций. Если какая-либо операция внутри транзакции выбрасывает ошибку, выполните ROLLBACK для отмены всех изменений. Многие ORM и библиотеки баз данных предоставляют встроенные помощники транзакций, которые автоматически откатывают при исключениях. Избегайте молчаливого подавления ошибок, так как это может оставить ваши данные в несогласованном состоянии.
Традиционные блокировки могут чаще приводить к взаимной блокировке читателей и писателей, что ограничивает конкурентность. MVCC избегает этого, сохраняя несколько версий каждой строки, чтобы читатели получали доступ к согласованному снимку без ожидания писателей. Компромисс заключается в том, что старые версии строк должны периодически очищаться, как это делает PostgreSQL со своим процессом VACUUM. MVCC обычно обеспечивает лучшую пропускную способность для нагрузок с интенсивным чтением.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.