Многоверсионность
Снимки данных
13
Авторские права
© Postgres Professional, 2016–2022.
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Видимость версий строк
Снимок данных
Виртуальный номер транзакции
Горизонт транзакции и базы данных
Экспорт снимка
3
Снимок
Дает согласованную картину данных на момент времени
неформально: видны только зафиксированные на этот момент данные
Создается:
Read Committed —
в начале каждого оператора
Repeatable Read, Serializable —
в начале первого оператора транзакции
снимок
время
В теме «Страницы и версии строк» мы видели, что в страницах данных
физически может находиться несколько версий одной и той же строки.
Транзакции работают со снимком данных, который определяет, какие
версии должны быть видны, а какие — нет, чтобы обеспечить
согласованную картину данных на определенный момент времени
(на момент создания снимка — показан на рисунке синим цветом).
Изоляция на основе снимков данных (snapshot isolation) требует, чтобы
транзакции были видны только те данные, которые были
зафиксированы на момент создания снимка. Это гарантирует, что
данные снимка будут согласованными (в обычном ACID-смысле).
На уровне изоляции Read Committed снимок создается в начале
каждого оператора транзакции. Снимок активен, пока выполняется
оператор.
На уровнях Repeatable Read и Serializable снимок создается один раз
в начале первого оператора транзакции. Такой снимок остается
активным до самого конца транзакции.
4
Видимость версий строк
Видимость версии строки ограничена xmin и xmax
Версия попадает в снимок, когда
изменения транзакции xmin видны для снимка,
изменения транзакции xmax не видны для снимка
Изменения транзакции видны, когда
либо это та же самая транзакция, что создала снимок,
либо она завершилась фиксацией до момента создания снимка
Отдельные правила для видимости собственных изменений
учитывается порядковый номер операции в транзакции (cmin/cmax)
Снимок данных не является физической копией нужных версий строк.
Будет или нет данная версия строки видна в снимке, определяется
двумя полями ее заголовка — xmin и xmax, — то есть номерами
создавшей и удалившей транзакций. Такие интервалы не
пересекаются, поэтому одна строка представлена в любом снимке
максимум одной своей версией.
Точные правила видимости довольно сложны и учитывают различные
«крайние» случаи. Чуть упрощая, можно сказать, что версия строки
видна, когда в снимке видны изменения, сделанные транзакцией xmin,
и не видны изменения, сделанные транзакцией xmax.
В свою очередь, изменения транзакции видны в снимке, если либо это
та же самая транзакция, что создала снимок (она сама видит свои же
изменения), либо транзакция была зафиксирована до создания снимка.
Несколько усложняет картину случай определения видимости
собственных изменений транзакции. Здесь может потребоваться
видеть только часть таких изменений. Например, курсор, открытый
в определенный момент, ни при каком уровне изоляции не должен
увидеть изменений, сделанных после этого момента. Для этого
в заголовке версии строки есть специальное поле (псевдостолбцы cmin
и cmax), показывающее номер операции внутри транзакции, и этот
номер тоже принимается во внимание.
5
Видимость версий строк
время
снимок
T3
T2
T1
Рассмотрим, как это работает, на простом примере.
На рисунке синим цветом отмечен момент создания снимка. Черные
линии показывают транзакции от момента начала до момента
фиксации (оборванные транзакции никогда не попадают в снимок).
В данном случае в снимке должны быть видны только изменения,
сделанные транзакцией T2, поскольку она зафиксирована до того, как
создан снимок.
Изменения транзакции T1 не должны быть видны, поскольку в момент
создания снимка она еще не была зафиксирована. А изменения
транзакции T3 не должны быть видны, поскольку она еще не началась
в момент создания снимка.
7
как отличить T1 от T2 после того, как снимок создан?
Видимость версий строк
время
снимок
момент окончания
транзакции неизвестен —
знаем лишь текущий статус
при создании снимка
T3
T2
T1
Сложность реализации состоит в том, что нам известен только момент
начала транзакции. Он определяется номером транзакции (xid),
с которым можно сравнить номер xmin или xmax версии строки.
Но момент завершения транзакции нигде не записывается. Все, что мы
может узнать — это текущий статус транзакций.
То есть после того, как транзакция T1 завершится фиксацией, она
ничем не будет отличаться от транзакции T2: нет способа выяснить, что
одна из них была зафиксирована до создания снимка, а другая —
после.
Поскольку историческая информация об активности транзакций нигде
не хранится, снимок можно создать только в текущий момент. Нельзя,
например, создать снимок, показывающий согласованные данные по
состоянию на пять минут назад, даже если все необходимые версии
строк существуют в табличных страницах.
8
Снимок данных
xmin — номер самой ранней активной транзакции
xmax — номер, следующий за последней зафиксированной транзакцией
xip list — список активных транзакций
xmaxxmin
xid
снимок
T1
T2
T3
видимость изменений
проверяется по списку
изменения
безусловно не видны
изменения
безусловно видны
xip list
В момент создания снимка данных запоминаются несколько значений,
которые и определяют снимок:
- xmin нижняя граница снимка, в качестве которой выступает номер
самой ранней активной транзакции.
Все транзакции с меньшими номерами либо зафиксированы, и тогда их
изменения безусловно видны в снимке, либо отменены, и тогда
изменения игнорируются.
- xmax — верхняя граница снимка, в качестве которой берется номер,
следующий за номером последней зафиксированной транзакции. Верх-
няя граница определяет момент, в который был сделан снимок.
Обратите внимание, что момент задается не временем (как было
условно показано на предыдущих слайдах), а увеличивающимися
номерами транзакций.
Все транзакции с номерами, большими или равными xmax, не
существовали в момент создания снимка, поэтому изменения таких
транзакций точно не видны.
- xip_list (xid-in-progress list) — список активных транзакций.
Этот список используется для того, чтобы не показывать в снимке
изменения транзакций, которые уже завершились, но в момент
создания снимка были еще активны.
Информацию о снимке можно получить с помощью функции
pg_current_snapshot() и нескольких других, см.
10
Виртуальные транзакции
Только читающая транзакция никак не влияет на видимость
обслуживающий процесс выделяет виртуальный номер
виртуальный номер не учитывается в снимках
виртуальный номер никогда не попадает в страницы
настоящий номер выделяется при первом изменении данных
На практике PostgreSQL использует оптимизацию, позволяющую
«экономить» номера транзакций.
Если транзакция только читает данные, то она никак не влияет на
видимость версий строк и ее номер не надо учитывать в снимках
данных.
Поэтому вначале обслуживающий процесс выдает транзакции
виртуальный номер (virtual xid). Номер состоит из идентификатора
процесса и последовательного числа.
Выдача этого номера не требует синхронизации между всеми
процессами и поэтому выполняется очень быстро. С другой причиной
использования виртуальных номеров мы познакомимся в теме
«Заморозка» этого модуля.
Такой виртуальный номер нельзя записывать в страницы данных,
потому что при следующем обращении к странице он может потерять
всякий смысл.
Как только транзакция начинает менять данные, ей сразу же выдается
настоящий номер, который может появляться в страницах данных
(в полях xmin и xmax версий строк).
12
Горизонт снимка
xid
неактуальные
версии строк
не нужны снимку
снимок
xmaxxmin
xip list
Номер самой ранней из активных транзакций (xmin снимка) имеет
важный смысл — он определяет горизонт снимка.
Горизонт событий в астрофизике — это граница, за которой
наблюдатель не может увидеть никакие события. А в нашем случае
горизонт снимка — это граница, за которой оператор, использующий
снимок, не может увидеть неактуальные версии строк.
Действительно, видеть неактуальную версию требуется только в том
случае, когда актуальная создана еще не завершившейся транзакцией,
и поэтому пока не видна. Но за горизонтом все транзакции уже
гарантированно завершились.
13
Горизонт транзакции
BEGIN SELECT
Repeatable Read, Serializable
xid
неактуальные
версии не нужны
транзакции
UPDATE
INSERT
виртуальный
номер
настоящий
номер
снимок
На разных уровнях изоляции транзакции по-разному используют снимки
данных.
Для уровней Repeatable Read и Serializable транзакция строит снимок
при первом обращении к серверу и использует его до своего окончания.
Левая граница снимка (начало горизонтального отрезка на слайде)
определяющая горизонт транзакции, равна номеру самой ранней
активной транзакции. Поскольку текущая транзакция сама является
активной, горизонт никогда не может оказаться правее настоящего
номера данной транзакции.
14
SELECT
Горизонт транзакции
Read Committed
неактуальные
версии не нужны
транзакции
xid
виртуальный
номер
BEGIN
снимок
В случае уровня изоляции Read Committed снимков может быть
несколько.
Сначала транзакция получает виртуальный номер. При первом
обращении к базе данных строится снимок.
15
Горизонт транзакции
Read Committed
xid
SELECTBEGIN
виртуальный
номер
По окончании запроса снимок освобождается. Поскольку в нашем
примере первый оператор транзакции не менял данные, она не
получила настоящий номер и не будет удерживать горизонт до начала
следующего оператора.
16
Горизонт транзакции
Read Committed
неактуальные
версии не нужны
транзакции
xid
SELECT UPDATE
виртуальный
номер
настоящий
номер
снимок
BEGIN
Если транзакция начинает менять данные, ей выдается настоящий
номер. В этот же момент строится новый снимок, горизонт которого
(xmin), как и всегда, находится на уровне первой активной транзакции.
Поскольку текущая транзакция сама является активной, горизонт
никогда не может оказаться правее номера данной транзакции.
17
Горизонт транзакции
Read Committed
неактуальные
версии строк
не нужны транзакции
xid
SELECT UPDATE
виртуальный
номер
настоящий
номер
BEGIN
Когда оператор завершает работу, снимок освобождается, а транзакция
держит горизонт на уровне своего номера на случай отката.
Если транзакция будет долго находиться в таком состоянии (оно
называется idle in transaction), не выполняя никаких команд, горизонт
будет оставаться на месте.