Коротко: ACID — требования к работе с данными (атомарность, согласованность, изолированность, надёжность), критичные для БД, особенно в финтехе.

ACID — это набор требований к системе работы с данными, гарантирующих их сохранность и валидность на уровне БД: Atomicity (атомарность), Consistency (согласованность), Isolation (изолированность) и Durability (надёжность). Особенно критичен ACID для финтеха, где неверные данные о транзакциях ведут к финансовым и репутационным потерям. Разберём каждую букву на жизненных примерах.

Как только разработчик вырастает из джуна и погружается в дебри архитектуры, проектирования и работы с БД глубже, чем CRUD, — он всё чаще слышит аббревиатуру ACID. Её же любят спрашивать на собеседованиях. ACID — это перечень требований к системе, призванный обеспечить сохранность и валидность данных на уровне БД. Это наиболее важно для финтеха: неправильные данные о финансовых транзакциях ведут и к финансовым потерям компании, и к репутационным, и к судебным издержкам.

Atomicity — атомарность

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

Разберём на примере. Есть платёжная система. Хуан из Гватемалы переводит деньги другу Пабло в Манаус. Что происходит на уровне системы? Со счёта Хуана списывается N песо, а на счёт Пабло должно поступить N песо — это два UPDATE-запроса в БД.

По «зелёному сценарию» оба запроса проходят без ошибок — проблем нет. А вот дальше начинаются приключения, и здесь два плохих сценария:

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

Оба сценария плохие. Сама по себе БД не знает, какие запросы связаны логически, если отправлять их по отдельности. Чтобы логически объединить запросы, существует механизм транзакций: он велит БД либо провести все запросы, обёрнутые в одну транзакцию, либо не проводить ни одного и при ошибке вернуть данные в исходное состояние. Так обеспечивается атомарность.

Consistency — согласованность

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

Пример. В платёжной системе есть клиенты, у них расчётные счета и карты. У клиента может быть N счетов, у счёта — N карт. Карты не существуют без привязки к счёту, счета — без клиента. Клиент с 10 счетами и 15 картами решает уйти, пишет заявление на закрытие всего и думает, что может спать спокойно. Но раз в бизнес-логике нет ни транзакций, ни foreign key в БД, менеджер удалил только запись в таблице clients, а 10 счетов и 15 карт остались «висеть».

Согласованность БД нарушена: в конце месяца бывший клиент получает счёт за обслуживание уже закрытых юридически счетов и карт — налицо репутационные потери. Решение: либо использовать foreign key (чтобы БД сама знала о связях), либо реализовать логику в транзакциях на уровне приложения, если таблицы нагруженные. На нагруженных таблицах foreign key лучше не использовать — он даёт ощутимую деградацию скорости.

Isolation — изолированность

Раньше мы рассматривали единичные последовательные запросы «в вакууме». В реальности к одной записи могут идти сотни запросов одновременно — например, к расчётному счёту юрлица. Тогда транзакции выполняются параллельно для ускорения системы. Иначе было бы так, что несколько бухгалтеров видели бы надпись «Вы хотите оплатить счёт? Ожидайте! Вы 5-й в очереди среди других бухгалтеров... но, возможно, ещё директор вклинится без очереди». Однако у параллельных запросов есть свои «приколы».

Потерянная запись

Схема атомарности транзакции: перевод 300 RUB, баланс со счёта 500 меняется на 800 RUB

На счёте 500 рублей. Первая транзакция списывает 300, в моменте получая остаток 200. Одновременно, ещё до её завершения, приходит вторая транзакция на пополнение 300 и, поскольку первая не закрыта, исходит из 500 + 300 = 800. Первая закрывается, следом вторая — итог 800 рублей вместо 500. Так выглядит эффект потерянной записи.

Грязное чтение

Схема отката транзакции: при откате баланс остаётся 500 RUB, изменения не применяются

На счёте 500 рублей. Идёт списание 300, и пока транзакция не закрыта, приходит запрос на чтение — он возвращает 500 − 300 = 200. Но потом запрос на запись падает, происходит откат: фактически на счёте остаётся 500. А тот, кто запрашивал остаток, увидел другую сумму. Это и есть грязное чтение.

Повторимое чтение

Схема изоляции: два графика читают данные, пока идёт изменение данных в БД

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

Фантомное чтение

Схема изоляции при добавлении/удалении данных: два графика читают данные во время записи

Разница между фантомным и повторимым чтением лишь в том, что при повторимом меняется только содержимое данных, а при фантомном — ещё и их количество.

Как с этим бороться?

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

Основные уровни изоляции (на примере MySQL):

  • READ UNCOMMITTED — самая слабая изоляция: данные доступны для чтения сразу после INSERT, ещё до COMMIT.
  • READ COMMITTED — данные можно прочитать только после COMMIT.
  • REPEATABLE READ — используется в MySQL по умолчанию; отличается тем, что добавленные данные доступны внутри транзакции, но не снаружи до подтверждения.
  • SERIALIZABLE — MySQL полностью блокирует каждую строку, над которой производится любое действие.

Durability — надёжность

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

ACID — это не теория ради собеседований, а базовые гарантии для данных, особенно в финтехе. Если проектируете систему, где критична целостность данных, — помогу разобраться на консультации.