Реляционная теория и 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 одинаково быстры.
В следующей статье мы посмотрим на практике, как работают транзакции.
Добавить комментарий