Как СУБД сохраняет атомарность транзакции при ошибке после нескольких успешно выполненных изменений?

Как СУБД сохраняет атомарность транзакции при ошибке после нескольких успешно выполненных изменений?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Атомарность достигается тем, что до фиксации транзакции СУБД может отменить все её изменения. Если ошибка делает транзакцию недействительной, изменения откатываются целиком; после успешного COMMIT транзакция считается единой завершённой операцией, а не набором независимо сохранённых шагов.

Исторический контекст

Транзакции появились как способ выполнять связанные изменения над данными без перехода базы в промежуточное или противоречивое состояние. Исходная проблема возникает при сбоях приложения, ошибках операций или отказе сервера между двумя изменениями одной логической операции.

Без атомарности первая часть операции могла бы сохраниться, а последующие изменения — нет. Для поддержки этого свойства СУБД используют журналирование, версии страниц, записи отмены или другие механизмы восстановления; конкретная реализация зависит от движка.

Постановка проблемы

Представим перевод средств: сумма списывается с одного счёта, затем должна зачислиться на другой. Если после списания происходит ошибка, частично выполненная транзакция приводит к потере средств или нарушению бизнес-инварианта.

Неверно считать границей атомарности отдельный SQL-оператор. Обычно ею является транзакция, но точное поведение ошибок, DDL и отдельных функций зависит от СУБД. Кроме того, откат изменений в базе не отменяет уже отправленное письмо, вызов внешнего сервиса или другое действие вне транзакции.

Подробное решение

Изменения сначала становятся частью рабочего состояния транзакции, но не считаются окончательно опубликованными для всех последующих операций восстановления. СУБД сохраняет достаточно информации в журнале или версиях данных, чтобы либо подтвердить весь набор изменений, либо восстановить прежнее состояние.

При явном или неявном откате СУБД применяет операции отмены ко всем изменениям данной транзакции. Если ошибка возникла внутри оператора, некоторые СУБД отменяют только этот оператор, а некоторые ошибки переводят всю транзакцию в состояние, из которого сначала требуется выполнить откат; полагаться на единое поведение без документации нельзя.

При COMMIT СУБД фиксирует факт завершения транзакции. Благодаря журналу и протоколу восстановления после сбоя она может отличить зафиксированную транзакцию от незавершённой: первую сохранить, вторую откатить или не публиковать.

Атомарность не равна изоляции и долговечности. Изоляция определяет, что видят конкурентные транзакции, а долговечность — сохранность подтверждённых изменений после сбоя. Атомарность также не распространяется автоматически на несколько независимых баз данных или внешние системы; для этого применяют координацию, например саги или распределённые транзакции, у каждой из которых есть свои компромиссы.

Ситуация из практики

Сервис оформляет заказ: создаёт заказ, уменьшает остаток товара и записывает платёж. Вариант с отдельным подтверждением каждого шага прост, но при сбое оставляет частично оформленный заказ. Вариант с одной транзакцией базы сохраняет согласованность данных, но удерживает блокировки дольше и не может сам отменить уже выполненный внешний платёж.

Практичное решение — объединить изменения внутри одной транзакции, а внешние действия связать с надёжной записью намерения в базе. После фиксации транзакции отдельный обработчик выполняет платёж или публикацию события, повторяя операцию безопасно благодаря идемпотентности. Так база сохраняет атомарность своих изменений, а интеграция получает контролируемое поведение при сбоях.

Что кандидаты часто упускают

1. Дополнительный вопрос: можно ли откатить транзакцию после успешного COMMIT?

Нет, обычный откат уже не отменяет зафиксированную транзакцию. После COMMIT изменения считаются частью состояния базы, поэтому компенсация выполняется новой транзакцией, которая записывает обратную бизнес-операцию.

Это важное различие между техническим откатом и компенсацией. Например, возврат платежа — не восстановление прежнего состояния базы механизмом rollback, а новая операция с собственной историей, проверками и возможностью отдельного сбоя.

2. Дополнительный вопрос: что происходит с незавершённой транзакцией при падении сервера?

При восстановлении СУБД анализирует журнал и состояние фиксации. Изменения транзакции, для которой нет подтверждённого факта фиксации, не должны остаться как завершённый результат; СУБД отменяет их или исключает из восстановленного состояния.

Изменения подтверждённой транзакции, напротив, восстанавливаются даже если часть данных не успела попасть на диск до сбоя. Для этого журнал обычно должен быть надёжно записан раньше соответствующих страниц данных, но детали порядка и настройки определяются конкретной СУБД.

3. Дополнительный вопрос: почему откат базы не отменяет вызов внешнего сервиса?

База данных и внешний сервис не участвуют автоматически в одной локальной транзакции. База может откатить свою запись, но уже отправленный HTTP-запрос, электронное письмо или списание у платёжного провайдера не исчезает.

Поэтому используют паттерн outbox: событие о необходимом внешнем действии записывают в ту же транзакцию, что и бизнес-изменения. После её фиксации обработчик читает событие и выполняет действие с повторными попытками и идемпотентным ключом; это не даёт абсолютной атомарности между системами, но делает сбои обнаружимыми и управляемыми.