Введение

Данная статья является четвертой частью. Предыдущие:

Как и прошлые части, данная является объединением книги и официальной документации с моими рисунками, объясняющими написанное в более наглядном (надеюсь, простом) варианте.

Информация взята из книги Егора Рогова PostgreSQL 16 изнутри, а также из документации PostgreSQL 16.2.

4.1. Что такое снимок данных

Физически в страницах данных могут находиться несколько версий одной и той же строки, хотя каждая транзакция должна видеть максимум одну из них. Все вместе версии разных строк, наблюдаемые транзакцией, образуют снимок данных (snapshot). Снимок обеспечивает согласованную в ACID смысле картину данных на определенный момент времени и содержит только самые актуальные данные, зафиксированные к моменту его создания.

Чтобы обеспечить изоляцию, каждая транзакция работает со своим собственным снимком. При этом разные транзакции видят разные, но тем не менее согласованные (на разные моменты времени) данные.

4.2. Видимость версий строк в снимке

Снимок данных не является физической копией всех необходимых версий строк. Фактически снимок задается несколькими числами, а видимость версий строк в снимке определяется правилами.

Будет данная версия строки видна в снимке или нет — зависит от полей xmin и xmax её заголовка (то есть от номеров создавшей и удалившей транзакций) и от соответствующих этим полям информационных битов. Интервалы xminxmax не пересекаются, поэтому каждая строка представлена в любом снимке максимум одной своей версией.

Точные правила видимости довольно сложны и учитывают множество различных ситуаций и крайних случаев.

На уровне изоляции Read Committed снимок создается в начале каждого оператора транзакции и остается активным все время работы этого оператора.

Рис. 4.1. - Изоляция снимков на уровне Read Committed.
Рис. 4.1. - Изоляция снимков на уровне Read Committed.

На уровнях Repeatable Read и Serializable снимок создается один раз в начале первого оператора транзакции и остается активным до самого конца транзакции.

Рис. 4.2. - Изоляция снимков на уровнях Repeatable Read и Serializable.
Рис. 4.2. - Изоляция снимков на уровнях Repeatable Read и Serializable.

4.3. Из чего состоит снимок

К сожалению, PostgreSQL видит картину совсем не так, как было показано на рисунках 4.1 и 4.2.

Дело в том, что системе неизвестно, когда транзакции были зафиксированы.
Известно только, когда они начинались.
Параметр track_commit_timestamp и журнальные записи не участвуют в проверке видимости.

Узнать можно лишь текущий статус транзакций. Эта информация есть в общей памяти сервера в структуре ProcArray, которая содержит список всех активных сеансов и их транзакций.

Поэтому для получения снимка недостаточно сохранить момент его создания: требуется также запомнить, в каком статусе находились транзакции на этот момент.

Рис. 4.3. - В момент создания снимка также смотрится список активных в данный момент транзакций.
Рис. 4.3. - В момент создания снимка также смотрится список активных в данный момент транзакций.

Если бы нам нужно было создать ретроспективный снимок для предыдущей транзакции (774) прямо сейчас (в момент выполнения 775) или позже, то этого не получилось бы сделать из-за неопределенного времени завершения транзакций (773, 772).

Без информации о статусах впоследствии невозможно будет понять, какие версии строк должны быть видны в снимке, а какие — нет.

Сейчас нам известно, что 773 и 772 завершены, однако нет информации что они были завершены так же и в момент снимка для 774.

Рис. 4.4. - Невозможно реализовать ретроспективные снимки.
Рис. 4.4. - Невозможно реализовать ретроспективные снимки.

По этой причине в PostgreSQL нельзя создать снимок, показывающий согласованные данные по состоянию на некоторый момент в прошлом, даже если все необходимые для этого версии строк существуют в табличных страницах. Соответственно, невозможно реализовать и ретроспективные (темпоральные, flashback) запросы.

Снимок данных состоит из нескольких значений, которые запоминаются в момент его создания.

  • Нижняя граница снимка xmin, в качестве которой выступает номер самой старой активной транзакции.

  • Верхняя граница снимка xmax, в качестве которой берется значение, на единицу большее номера последней зафиксированной транзакции. Верхняя граница определяет момент времени, в который был сделан снимок.

  • Список активных транзакций xip_list (xid-in-progress list), в который попадают номера всех активных транзакций меньших xmax, за исключением виртуальных, которые никак не влияют на видимость.

Рис. 4.5. - Детальный разбор значений при создании снимков.
Рис. 4.5. - Детальный разбор значений при создании снимков.

Как PostgreSQL понимает, какие версии показывать? По сформулированным выше правилам в снимке видны изменения только следующих зафиксированных транзакций:

  • с номерами xid < xmin;

  • с номерами xminxid < xmax — за исключением попавших в список xip_list.

4.5. Горизонт транзакции

Нижняя граница снимка (номер xmin самой ранней транзакции, активной на момент его создания) имеет важный смысл — она определяет горизонт транзакции, работающей с этим снимком.

Все транзакции, находящиеся за горизонтом (то есть транзакции с номе-рами xid < xmin), уже гарантированно зафиксированы. А это значит, что за своим горизонтом транзакция всегда видит только актуальные версии строк.

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

Рис. 4.6. - Горизонт транзакции.
Рис. 4.6. - Горизонт транзакции.

Похожим образом можно определить и горизонт базы данных. Для этого надо взять горизонты всех транзакций, работающих с этой базой, и среди них найти наиболее «дальний», имеющий самый старый xmin. Это и будет тот горизонт, за которым неактуальные версии строк в этой базе данных уже никогда не будут видны ни одной транзакции. Такие версии строк могут быть безопасно удалены очисткой — именно поэтому понятие горизонта так важно с практической точки зрения.

При этом на всю базу данных есть только один горизонт, и если какая-то транзакция его удерживает — она не дает очищать данные внутри этого горизонта, даже те, к которым не обращалась.

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

Рис. 4.7 - Горизонт базы данных.
Рис. 4.7 - Горизонт базы данных.

Одной из причин раздувания файлов является совмещение долго выполняющихся транзакций, удерживающих горизонт базы, с активным обновлением данных.

4.6. Снимок данных для системного каталога

Хотя системный каталог и представлен обычными таблицами, при обращении к ним нельзя задействовать тот же снимок данных, что используется транзакцией или оператором. Снимок должен быть достаточно «свежим», чтобы включать все последние изменения, ведь иначе транзакция могла бы увидеть уже неактуальное определение столбцов таблицы или пропустить созданное ограничение целостности.

Рис. 4.8. - Снимок данных для системного каталога.
Рис. 4.8. - Снимок данных для системного каталога.

Команда INSERT «увидела» ограничение целостности, которое появилось уже после того, как был создан снимок данных для транзакции 776.

В каталоге pg_attribute хранится информация о столбцах таблицы.

В целом система ведет себя так, как будто для каждого обращения к системному каталогу создается новый снимок.

4.7. Экспорт снимка данных

Бывают ситуации, когда несколько параллельных транзакций должны гарантированно видеть одну и ту же картину данных.

Разумеется, нельзя полагаться на то, что картины данных совпадут просто потому, что транзакции запущены «одновременно». Такие гарантии дает механизм экспорта и импорта снимка.

Рис. 4.9. - Экспорт снимка данных для его последующей передачи другой транзакции.
Рис. 4.9. - Экспорт снимка данных для его последующей передачи другой транзакции.

Функция pg_export_snapshot возвращает идентификатор снимка, который может быть передан в другую транзакцию.

SELECT pg_export_snapshot();
pg_export_snapshot
−−−−−−−−−−−−−−−−−−−−−
00000004−0000006E−1

Другая транзакция может импортировать снимок с помощью команды:

BEGIN ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION SNAPSHOT '00000004-0000006E-1';

Разумеется, изменения, сделанные первой транзакцией после экспорта снимка, не будут видны второй транзакции (и наоборот).

Заключение

В данной статье была рассмотрена «Глава 4. Снимки данных» из книги PostgreSQL 16 изнутри.

В дальнейшем будет рассмотрена глава 5 — «Внутристраничная очистка и hot-обновления» этой же книги.

Комментарии (0)