Транзакции в базах данных и ACID
Транзакции поддерживают все реляционные базы данных и некоторые NoSQL базы. Представляют они из себя простой набор команд, который должен быть выполнен как одно целое. В большинстве случает транзакция представляет некую бизнес операцию.
Базы данных гарантируют что все запросы в рамках транзакции выполняться как одно целое или не выполняться вообще. Отталкиваясь от этой концепции был придуман набор требований к транзакциям и системам, которые их используют. Эти правила гарантируют надежную работу транзакций. Такой набор требований называется ACID.
ACID гарантирует что данные в БД будут целостные независимо от любых сбоев.
Всего есть четыре свойства у транзакций:
- Atomicity (атомарность) — команды внутри транзакции буду выполнены все вместе или ни одной. То есть транзакция это атомарная команда. Достигается это за счет системы откатов и журнала транзакций. Если внутри транзакции какой-то запрос выполнился с ошибкой, то все изменения, сделанные в рамках этой транзакции откатываются.
- Сonsistency (консистентность) — данные должны быть консистентными после выполнения транзакции. Это значит что в них нету логических и технических противоречий. Например: суммарный баланс счетов должен оставаться неизменным, это логическая целостность. Записи в таблицах не ссылаются на удаленные идентификаторы в другой таблице — техническая целостность.
- Isolation (изолированность) — транзакции зачастую обрабатываются параллельно, это значит что они не должны влиять друг на друга. Так если два человека одновременно пересылают деньги третьему, то одна транзакция может переписать данные другой и деньги потеряются. На практике изоляция достигается за счет уровней изоляции.
- Durability (стойкость) — если транзакция завершена успешно, то она не может быть отменена даже при авариях, внезапном отключении света в датацентре и проблем в сети. В этом случае база данных должна сама восстановить последние изменения.
Отдельно стоит упомянуть уровни изоляции, потому что их понимание позволяет находить баги в коде, который работает с базой. Самих уровней существую большое множество, но рассмотрим четыре основных:
- read uncommited — позволяет избежать потерянных обновлений, когда две транзакции изменяют одни и те же данные. Для этого одна транзакция блокирует данные, которые хочет изменить для других UPDATE операций в других транзакциях.
- read committed — решает проблему грязного чтения, когда вычитываем данные во время их обновления. Это может привести к тому что мы получим частично обновленные данные. В таком случае UPDATE операции в транзакции блокируют UPDATE и SELECT операции в других транзакциях. Именно этот уровень изоляции используется по умолчанию в большинстве БД.
- repeatable read — внутри транзакции может быть несколько операций SELECT, которые читают одни и те же данные. Repeatable read гарантирует что это операции внутри одной транзакции будут возвращать одинаковые данные. Даже если другие транзакции хотят их удалить или изменить. В таком случает транзакция блокирует все строчки, которые затрагивают операции UPDATE и SELECT.
- serializable — исключает проблему «фантомных чтений», которые возникают при вставке новой строки между двумя операциями SELECT внутри одной транзакции. Больше всего эта проблема влияет на агрегационные запросы. Такой уровень изоляции является самым строгим, все транзакции выполняются так, будто других параллельных транзакций не существует.