Базы данных, транзакции и ACID
Транзакция — набор команд, которые должны быть выполнены одной пачкой, так как представляют собой одну бизнес операцию.
Пример: транзакция по переводу денег состоит из команд «списать деньги с отправителя», «начислисть деньги адресату».
ACID — это требования к транзакциям и системам, работающим с ними (например, базам данных). ACID требует, чтобы каждая транзакция
- не зависала в середине пути в случае ошибки, а откатывала все сделанные изменения (атомарность),
- после своего завершения не оставляла данные неконсистентными (консистентность),
- не влияла на другие транзакции (на самом деле влияла, но как можно меньше — 4 уровня изолированности),
- а вся система гарантировала, что выполненные транзакции будут запомнены системой даже при возникновении аварий и форс-мажоров (стойкость, durability).
Подробнее о каждом требовании и с примерами
-
Atomicity/Атомарность требует, чтобы либо все команды транзакции были выполнены, либо ни одной. То есть транзакция должна действовать как единая атомарная команда.
На практике атомарность реализуется через версионирование и откаты (rollback) команд транзакции до первоначального состояния базы. Строго говоря, индексы могут обратно не откатиться, но чаще всего СУБД это разруливают сами. -
Сonsistency/Консистентность требует, чтобы после завершения транзакции данные оставались консистентными и валидными, т. е. чтобы они не имели логических или технических противоречий.
Пример: суммарный баланс счетов должен оставаться неизменным (логическая К.), запись одной таблицы не должна ссылаться на удалённый айдишник другой записи (техническая К.). -
Isolation/Изолированность — при параллельном выполнении транзакции не должны влиять друг на друга.
Пример: если два человека одновременно делают денежный перевод третьему, то одна транзакция в теории может перезаписать значения другой, и деньги потеряются. Изолированность исключает такую ситуацию.
- Durability/Стойкость — если транзакция завершена успешно, то она не может быть отменена даже при авариях, внезапном отключении света в датацентре и проблем в сети. В этом случае база данных должна сама восстановить последние транзакции.
Уровни изолированности в базах данных
На практике изолированность сложна в реализации и сильно влияет на производительность системы. Поэтому базы данных могут работать с четырьмя уровнями изолированности (от меньшей надёжности к большей).
-
Read uncommitted — позволяет избежать «потерянных обновлений», когда две транзакции обновляют одно и то же значение/строку.
На этом уровне UPDATE-транзакции резервируют данные для себя и блокируют для других UPDATE-транзакций. Тем не менее, SELECT-запросы не блокируются и даже могут считать промежуточное состояние данных, возникающие между выполнением команд одной транзакции.
Пример: транзакция на перевод денег состоит из команды на списание денег с одного счёта и команды пополнения другого счёта. На момент изменения баланса счёта первой транзакцией вторая подобная транзакция встанет в очередь, пока данные не освободятся. Но SELECT-запрос может считать состояние, когда деньги списаны с одного счёта, но на второй ещё не зачислены.
-
Read committed — то же, что выше + решает проблему чтения «грязных» состояний незавершённых транзакций. Большинство баз данных по умолчанию работает на этом уровне изолированности.
Однако если транзакция содержит две SELECT-команды и во время между ними другая UPDATE-транзакция успешно завершится, то результат этих SELECT-запросов может отличваться: один вернёт состояние до UPDATE-транзакции, второй — после. Важно, что это не «грязное» состояние, а чистое, после успешной транзакции.
-
Repeatable read (повторяемость чтения) — оба пункта выше + гарантирует, что SELECT-запросы в рамках одной транзакции всегда будут возвращать один и тот же результат, даже если другие транзакции обновляют или удаляют эти же данные.
Транзакция блокирует все строки, затрагиваемые её командами, включая SELECT, а другие транзакции с SELECT-, UPDATE- и DELETE-запросами к этим данным ждут её завершения. Естественно, это сильно снижает скорость обработки транзакций базой данных.
-
Serializable — три пункта выше + исключает «фантомные чтения».
«Фантомное чтение» похоже на проблему с двумя последовательными SELECT-запросами одной транзакции, но возникает, когда между SELECT-запросами была выполнена именно вставка (INSERT). Пример: аггрегационные запросы SELECT SUM(), SELECT COUNT().
Это максимальный уровень изолированности. При нём транзакции выполняются так, будто других параллельных транзакций не существует.