Транзакции в PostgreSQL

Реляционная теория и SQL позволяет абстрагироваться от конкретной реализации СУБД, но есть одна непростая проблема: как обеспечить параллельную работу множества сессий (concurrency), которые модифицируют данные, так, чтобы они не мешали друг другу ни с точки зрения чтения, ни с точки зрения записи и обеспечивали целостность данных (consistency) и их надежность (durability)?

Ответ — транзакционные системы OLTPOnLine Transaction Processing. Они отвечают следующему принципу ACID:

  • Atomicity — атомарность
  • Consistency — согласованность
  • Isolation — изолированность
  • Durability — долговечность

Соответственно, транзакция (transaction) это:

  • множество операций, выполняемое приложением
  • которое переводит базу данных из одного корректного состояния в другое корректное состояние (согласованность)
  • при условии, что транзакция выполнена полностью (атомарность)
  • и без помех со стороны других транзакций (изолированность)

Всё было бы хорошо, но что делать с блокировками и сбоями?

Для этого придуманы следующие принципы:

ARIESAlgorithms 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 одинаково быстры.

В следующей статье мы посмотрим на практике, как работают транзакции.

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

шесть + семнадцать =