Реляционная теория и SQL позволяет абстрагироваться от конкретной реализации СУБД, но есть одна непростая проблема: как обеспечить параллельную работу множества сессий (concurrency), которые модифицируют данные, так, чтобы они не мешали друг другу ни с точки зрения чтения, ни с точки зрения записи и обеспечивали целостность данных (consistency) и их надежность (durability)?
Ответ – транзакционные системы OLTP – OnLine Transaction Processing. Они отвечают следующему принципу ACID:
- Atomicity – атомарность
- Consistency – согласованность
- Isolation – изолированность
- Durability – долговечность
Соответственно, транзакция (transaction) это:
- множество операций, выполняемое приложением
- которое переводит базу данных из одного корректного состояния в другое корректное состояние (согласованность)
- при условии, что транзакция выполнена полностью (атомарность)
- и без помех со стороны других транзакций (изолированность)
Всё было бы хорошо, но что делать с блокировками и сбоями?
Для этого придуманы следующие принципы:
ARIES – Algorithms for Recovery and Isolation Exploiting Semantics. Алгоритмы эффективного восстановления после сбоев.
Использует следующие механизмы:
- logging – логирование
- undo – сегменты отката транзакций
- redo – сегменты проигрывания транзакций после сбоя
- checkpoints – система контрольных точек
MVCC – MultiVersion Concurrency Control. Конкурентный контроль мультиверсионности. Использует следующие механизмы:
- copy-on-write – создаётся копия старых данных при записи или модификации
- каждый пользователь работает со снимком БД
- вносимые пользователем изменения не видны другим до фиксации транзакции
- практически не использует блокировок (только одна – писатель блокирует писателя, если они пытаются работать с одной записью)
- или ручная блокировка при вызове команды select for update
Механизм реализации MVCC в PostgreSQL.
Данные хранятся поблочно и посмотрим, из чего состоит один блок:
В самом начале идёт служебный блок, состоящий из:
- пары идентификаторов транзакций по 4 байта
- ряда информационных байтов
- пары информационных масок
- пустой битовой карты для будущего использования в следующих версиях PostgreSQL
- данных (более подробно будем рассматривать в следующих темах).
Если рассматривать подробно, то заголовок состоит из:
- xmin – идентификатор транзакции, которая создала данную версию записи
- xmax – идентификатор транзакции, которая удалила данную версию записи
- cmin – порядковый номер команды в транзакции, добавившей запись
- cmax – номер команды в транзакции, удалившей запись
Соответственно, когда мы выполняем операции вставки, обновления или удаления записи в PostgreSQL, происходят следующие изменения этих значений:
- Insert – добавляется новая запись с xmin = txid_current() и xmax = 0
- Update – в старой версии записи xmax = txid_current(), то есть делается delete, добавляется новая запись с xmin = txid_current() и xmax = 0, то есть делается insert
- Delete – в старой версии записи xmax = txid_current()
Следовательно, при добавлении записи у нас происходит две операции: старая запись помечается как удалённая (проставляется xmax), и происходит добавление новой записи. При удалении проставляется значение xmax, при этом физическое удаление данных не производится.
Для фиксации или отмены транзакции нам нужно рассмотреть ещё дополнительные атрибуты строки – infomask содержит ряд битов, определяющих свойства данной версии:
- xmin_commited, xmin_aborted – xmin подтверждён или отменён
- xmax_commited, xmax_aborted – xmax подтверждён или отменён
Важно отметить поле ctid – ссылка на следующую, более новую, версию той же строки. У самой новой, актуальной, версии строки ctid ссылается на саму эту версию. Номера ctid имеют вид (x,y): здесь x – номер страницы, y – порядковый номер указателя в массиве
Каждая транзакция может завершиться:
- успешно – после команды commit, для этого, в зависимости от типа операции, проставляются биты xmin_commited, xmax_commited
- неуспешно из-за, например, ошибки доступа к данным или вызове команды rollback.
При неуспешном завершении нам нужно откатить все изменения, выполненные этой транзакцией. При этом в случае с PostgreSQL нам практически не нужно ничего делать – будут установлены биты подсказки xmin_aborted, xmax_aborted. Сам номер xmax при этом остаётся в странице, но смотреть на него уже никто не будет.
Так что операции как коммита, так и роллбэка транзакции в PostgreSQL одинаково быстры.
В следующей статье мы посмотрим на практике, как работают транзакции.
Добавить комментарий