F.46. pg_ivm#
F.46. pg_ivm #
Модуль pg_ivm
предоставляет функцию Инкрементального Обслуживания Представлений (IVM) для Tantor SE.
F.46.2. Описание #
Инкрементное Обслуживание Представлений
(IVM) — это способ поддержания актуальности материализованных представлений,
при котором вычисляются и применяются только инкрементные изменения,
а не пересчитывается содержимое с нуля, как это делает
REFRESH MATERIALIZED VIEW
. IVM может обновлять
материализованные представления более эффективно, чем пересчет, когда изменяются
только небольшие части представления.
Существует два подхода к времени обновления представлений: немедленный и отложенный. При немедленном обновлении представления обновляются в той же транзакции, в которой изменяется базовая таблица. При отложенном обновлении представления обновляются после фиксации транзакции, например, когда к представлению осуществляется доступ, в ответ на команду пользователя, такую как REFRESH MATERIALIZED VIEW
, или периодически в фоновом режиме и так далее. pg_ivm
предоставляет своего рода немедленное обновление, при котором материализованные представления обновляются немедленно в триггерах AFTER, когда изменяется базовая таблица.
Мы называем материализованное представление, поддерживающее IVM,
Инкрементально Поддерживаемым
Материализованным Представлением (IMMV). Чтобы создать IMMV, вы должны вызвать
функцию create_immv
с именем отношения и
запросом определения представления. Например:
SELECT pgivm.create_immv('myview', 'SELECT * FROM mytab');
создает IMMV с именем ‘myview’, определенным как
SELECT * FROM mytab
. Это соответствует
следующей команде для создания обычного материализованного представления;
CREATE MATERIALIZED VIEW myview AS SELECT * FROM mytab;
Когда создается IMMV, некоторые триггеры создаются автоматически, чтобы содержимое представления немедленно обновлялось при изменении его базовых таблиц.
postgres=# SELECT pgivm.create_immv('m', 'SELECT * FROM t0'); NOTICE: could not create an index on immv "m" automatically DETAIL: This target list does not have all the primary key columns, or this view does not contain DISTINCT clause. HINT: Create an index on the immv for efficient incremental maintenance. create_immv ------------- 3 (1 row) postgres=# SELECT * FROM m; i --- 1 2 3 (3 rows) postgres=# INSERT INTO t0 VALUES (4); INSERT 0 1 postgres=# SELECT * FROM m; -- automatically updated i --- 1 2 3 4 (4 rows)
Примечание
Во время автоматического обслуживания IMMV, search_path
временно изменяется на pg_catalog, pg_temp
.
F.46.3. Установка #
Выполните команду CREATE EXTENSION
.
CREATE EXTENSION pg_ivm;
F.46.3.1. Конфигурация #
Чтобы гарантировать, что pg_ivm
правильно поддерживает IMMVs,
добавьте его в shared_preload_libraries или
session_preload_libraries в postgresql.conf:
# Add pg_ivm to preload libraries shared_preload_libraries = 'pg_ivm' # OR session_preload_libraries = 'pg_ivm'
После внесения этого изменения перезапустите PostgreSQL, чтобы конфигурация вступила в силу.
F.46.4. Объекты #
Когда установлен pg_ivm
, создается схема ‘pgivm’ вместе со следующими объектами в этой схеме.
F.46.4.1. Функции #
F.46.4.1.1. create_immv #
Используйте функцию create_immv
для создания IMMV.
pgivim.create_immv(immv_name text, view_definition text) RETURNS bigint
create_immv
определяет новый IMMV для запроса.
Создается таблица с именем immv_name
,
и выполняется запрос, указанный в view_definition
,
который используется для заполнения IMMV. Запрос сохраняется в
pg_ivm_immv
, чтобы его можно было обновить
позже при инкрементальном обслуживании представлений.
create_immv
возвращает количество строк в
созданном IMMV.
Когда создается IMMV, некоторые триггеры автоматически создаются, чтобы содержимое представления немедленно обновлялось при изменении его базовых таблиц. Кроме того, уникальный индекс создается на IMMV автоматически, если это возможно. Если в запросе определения представления есть предложение GROUP BY, уникальный индекс создается на столбцах выражений GROUP BY. Также, если в представлении есть предложение DISTINCT, уникальный индекс создается на всех столбцах в целевом списке. В противном случае, если IMMV содержит все атрибуты первичного ключа своих базовых таблиц в целевом списке, уникальный индекс создается на этих атрибутах. В других случаях индекс не создается.
create_immv
захватывает
AccessExclusiveLock
на представление. Однако,
даже если нам удается захватить блокировку, параллельная
транзакция могла уже инкрементально обновить и зафиксировать представление до того, как мы сможем его захватить. На уровнях изоляции
REPEATABLE READ
или
SERIALIZABLE
это может
привести к неконсистентному состоянию представления. К сожалению, эта
ситуация не может быть обнаружена во время создания представления.
В результате, create_immv
выдает предупреждение
на этих уровнях изоляции, предлагая использовать команду
в READ COMMITTED
или выполнить
refresh_immv
после, чтобы
содержимое представления оставалось согласованным.
Примечание
Пока create_immv
выполняется,
search_path
временно изменяется на
pg_catalog, pg_temp
.
F.46.4.1.2. refresh_immv #
Используйте функцию refresh_immv
для обновления IMMV.
pgivm.refresh_immv(immv_name text, with_data bool) RETURNS bigint
refresh_immv
полностью заменяет
содержимое IMMV, как
команда REFRESH MATERIALIZED VIEW
делает для
материализованного представления. Чтобы выполнить эту функцию, вы должны быть
владельцем IMMV (в PostgreSQL 16 или ранее) или иметь
привилегию MAINTAIN
на IMMV (в
PostgreSQL 17 или позже). Старое содержимое удаляется.
Флаг with_data соответствует
WITH [NO] DATA
опции команды REFRESH
MATERIALIZED VIEW. Если with_data истинно, выполняется
поддерживающий запрос для предоставления новых данных, и если
IMMV не заполнен, создаются триггеры для поддержания представления.
Также создается уникальный индекс для IMMV, если это возможно и
представление его еще не имеет. Если with_data ложно, новые данные
не генерируются, и IMMV становится незаполненным, а триггеры
удаляются из IMMV. Обратите внимание, что незаполненный IMMV
все еще может быть просканирован, хотя результат будет пустым.
Это поведение может быть изменено в будущем, чтобы вызывать ошибку
при сканировании незаполненного IMMV.
refresh_immv
приобретает
AccessExclusiveLock
на представление. Однако,
даже если мы можем получить блокировку, параллельная
транзакция могла уже инкрементально обновить и
зафиксировать представление до того, как мы сможем получить блокировку. На
уровне изоляции REPEATABLE READ
или
SERIALIZABLE
это может
привести к неконсистентному состоянию представления. Поэтому возникает ошибка,
чтобы предотвратить аномалии, когда эта ситуация
обнаружена.
Примечание
Пока refresh_immv
выполняется,
search_path
временно изменяется на
pg_catalog, pg_temp
.
F.46.4.1.3. get_immv_def #
get_immv_def
реконструирует исходную
команду SELECT для IMMV. (Это декомпилированная
реконструкция, а не оригинальный текст команды.)
pgivm.get_immv_def(immv regclass) RETURNS text
F.46.4.2. Каталог метаданных IMMV #
Каталог pgivm.pg_ivm_immv
хранит информацию IMMV.
Имя | Тип | Описание |
---|---|---|
immvrelid | regclass | OID IMMV |
viewdef | text | Дерево запроса (в виде представления nodeToString()) для определения представления |
ispopulated | bool | Истина, если IMMV в настоящее время заполнен |
lastivmupdate | xid8 | Идентификатор транзакции последнего инкрементного обновления представления |
F.46.5. Пример #
F.46.5.1. CREATE MATERIALIZED VIEW
и
REFRESH MATERIALIZED VIEW
#
В общем, IMMVs позволяют более быстрые обновления, чем
REFRESH MATERIALIZED VIEW
, за счет
более медленных обновлений их базовых таблиц. Обновление базовых таблиц
медленнее, потому что будут вызываться триггеры, и IMMV обновляется
в триггерах для каждого оператора модификации.
Например, предположим, что обычное материализованное представление определено как ниже:
test=# CREATE MATERIALIZED VIEW mv_normal AS SELECT a.aid, b.bid, a.abalance, b.bbalance FROM pgbench_accounts a JOIN pgbench_branches b USING(bid); SELECT 10000000
Обновление кортежа в базовой таблице этого материализованного представления
происходит быстро, но команда REFRESH MATERIALIZED VIEW
для этого представления занимает много времени:
test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1; UPDATE 1 Time: 9.052 ms test=# REFRESH MATERIALIZED VIEW mv_normal ; REFRESH MATERIALIZED VIEW Time: 20575.721 ms (00:20.576)
F.46.5.2. Создание IMMV #
С другой стороны, после создания IMMV с тем же определением представления, как показано ниже:
test=# SELECT pgivm.create_immv('immv', 'SELECT a.aid, b.bid, a.abalance, b.bbalance FROM pgbench_accounts a JOIN pgbench_branches b USING(bid)'); NOTICE: created index "immv_index" on immv "immv" create_immv ------------- 10000000 (1 row)
Обновление кортежа в базовой таблице занимает больше времени, чем в обычном
представлении, но его содержимое обновляется автоматически, и это
быстрее, чем команда REFRESH MATERIALIZED VIEW
.
test=# UPDATE pgbench_accounts SET abalance = 1234 WHERE aid = 1; UPDATE 1 Time: 15.448 ms test=# SELECT * FROM immv WHERE aid = 1; aid | bid | abalance | bbalance -----+-----+----------+---------- 1 | 1 | 1234 | 0 (1 row)
Необходим соответствующий индекс на IMMV для эффективного IVM, потому что нам нужно искать кортежи для обновления в IMMV. Если индексов нет, это займет много времени.
Поэтому, когда IMMV создается с помощью
функции create_immv
, уникальный индекс
создается на нем автоматически, если это возможно. Если в запросе определения представления
есть предложение GROUP BY, уникальный индекс создается на
столбцах выражений GROUP BY. Также, если в представлении есть предложение DISTINCT,
уникальный индекс создается на всех столбцах в целевом списке. В противном случае, если IMMV содержит все атрибуты первичного ключа
своих базовых таблиц в целевом списке, уникальный индекс создается
на этих атрибутах. В других случаях индекс не создается.
В предыдущем примере уникальный индекс “immv_index” создается на столбцах aid и bid таблицы “immv”, и это позволяет быстро обновлять представление. Удаление этого индекса делает обновление представления более длительным.
test=# DROP INDEX immv_index; DROP INDEX test=# UPDATE pgbench_accounts SET abalance = 9876 WHERE aid = 1; UPDATE 1 Time: 3224.741 ms (00:03.225)
F.46.5.3. IMMV с агрегатными функциями #
Вы можете создать IMMV, который включает агрегатные функции.
test=# SELECT pgivm.create_immv('immv_agg', 'SELECT bid, count(*), sum(abalance), avg(abalance) FROM pgbench_accounts JOIN pgbench_branches USING(bid) GROUP BY bid'); NOTICE: created index "immv_agg_index" on immv "immv_agg" create_immv ------------- 100 (1 row) Time: 5772.625 ms (00:05.773)
Создание этого представления занимает около пяти секунд, и обычная
операция обновления требует аналогичного количества времени. В
следующем примере демонстрируется обновление IMMV с использованием
refresh_immv
. Время выполнения будет
примерно таким же, если бы вы использовали
REFRESH MATERIALIZED VIEW
на обычном
материальном представлении с тем же определением.
test=# SELECT pgivm.refresh_immv('immv_agg',true); refresh_immv -------------- 100 (1 row) Time: 5766.292 ms (00:05.766)
Когда базовая таблица обновляется, IMMV также автоматически обновляется инкрементно, как и ожидалось.
test=# SELECT bid, count, sum, avg FROM immv_agg WHERE bid = 42; bid | count | sum | avg -----+--------+-------+------------------------ 42 | 100000 | 38774 | 0.38774000000000000000 (1 row) Time: 3.123 ms test=# UPDATE pgbench_accounts SET abalance = abalance + 1000 WHERE aid = 4112345 AND bid = 42; UPDATE 1 Time: 16.616 ms test=# SELECT bid, count, sum, avg FROM immv_agg WHERE bid = 42; bid | count | sum | avg -----+--------+-------+------------------------ 42 | 100000 | 39774 | 0.39774000000000000000 (1 row) Time: 1.987 ms
После обновления строки в таблице pgbench_accounts
вы можете увидеть, что агрегированные значения в
immv_agg
обновляются автоматически.
F.46.5.4. Перечисление IMMVs и просмотр их определений #
Вы можете найти все IMMVs в каталоге pg_ivm_immv
и просмотреть их определяющие запросы, выполнив
функцию get_immv_def
.
test=# SELECT immvrelid AS immv, pgivm.get_immv_def(immvrelid) AS immv_def FROM pgivm.pg_ivm_immv; immv | immv_def ----------+-------------------------------------------- immv_agg | SELECT pgbench_accounts.bid, + | count(*) AS count, + | sum(pgbench_accounts.abalance) AS sum,+ | avg(pgbench_accounts.abalance) AS avg + | FROM (pgbench_accounts + | JOIN pgbench_branches USING (bid)) + | GROUP BY pgbench_accounts.bid immv | SELECT a.aid, + | b.bid, + | a.abalance, + | b.bbalance + | FROM (pgbench_accounts a + | JOIN pgbench_branches b USING (bid)) (2 rows)
F.46.5.5. Удаление IMMV #
IMMV можно удалить, используя команду DROP TABLE
.
После удаления IMMV его запись автоматически
удаляется из каталога pg_ivm_immv
.
test=# DROP TABLE immv; DROP TABLE test=# SELECT immvrelid AS immv, pgivm.get_immv_def(immvrelid) AS immv_def FROM pgivm.pg_ivm_immv; immv | immv_def ----------+-------------------------------------------- immv_agg | SELECT pgbench_accounts.bid, + | count(*) AS count, + | sum(pgbench_accounts.abalance) AS sum,+ | avg(pgbench_accounts.abalance) AS avg + | FROM (pgbench_accounts + | JOIN pgbench_branches USING (bid)) + | GROUP BY pgbench_accounts.bid (1 row)
F.46.6. pg_dump
и
pg_upgrade
#
После восстановления данных из резервной копии pg_dump
или
обновления PostgreSQL
с использованием
pg_upgrade
, все IMMVs должны быть вручную удалены
и воссозданы.
F.46.7. Поддерживаемые Определения Представлений и Ограничения #
В настоящее время определение представления IMMV может содержать внутренние соединения,
оператор DISTINCT, некоторые встроенные агрегатные функции, простые
подзапросы в FROM
операторе, подзапросы EXISTS,
и простые CTE (WITH
запрос). Поддерживаются внутренние соединения,
включая самосоединение, но внешние соединения не поддерживаются. Поддерживаемые агрегатные функции: count, sum, avg, min
и max. Другие агрегаты, подзапросы, содержащие агрегат или оператор DISTINCT
, подзапросы в других операторах, кроме
FROM
, оконные функции,
HAVING
, ORDER BY
,
LIMIT
/OFFSET
,
UNION
/INTERSECT
/EXCEPT
,
DISTINCT ON
, TABLESAMPLE
,
VALUES
, и
FOR UPDATE
/SHARE
не могут быть
использованы в определении представления.
Базовые таблицы должны быть простыми таблицами. Представления, материализованные представления, родительские таблицы наследования, секционированные таблицы, секции и внешние таблицы не могут быть использованы.
Любая системная колонка не может быть включена в запрос определения представления.
Список целевых колонок не может содержать колонки, имя которых начинается с
__ivm_
.
Тип данных, используемый в целевом списке в представлении, должен иметь класс оператора по умолчанию для метода доступа btree. Например, типы json
, xml
или point
не могут быть в целевом списке.
Логическая репликация не поддерживается, то есть даже когда базовая таблица на узле издателя изменяется, IMMVs на узлах подписчика, определенные на этих базовых таблицах, не обновляются.
F.46.8. Примечания #
F.46.8.1. Агрегатные функции #
Поддерживаемые агрегатные функции: count
,
sum
, avg
,
min
и max
. В настоящее время
поддерживаются только встроенные агрегатные функции, и пользовательские
агрегаты не могут быть использованы.
Когда создается IMMV, включающий агрегат, некоторые дополнительные столбцы, имена которых начинаются с __ivm
, автоматически добавляются в целевой список. __ivm_count__
содержит количество кортежей, агрегированных в каждой группе. Кроме того, для каждого столбца агрегированного значения добавляется более одного дополнительного столбца для поддержания значения. Например, столбцы с именами, такими как __ivm_count_avg__
и __ivm_sum_avg__
, добавляются для поддержания среднего значения. Когда базовая таблица изменяется, новые агрегированные значения вычисляются инкрементально, используя старые агрегированные значения и значения связанных дополнительных столбцов, хранящихся в IMMV.
Обратите внимание, что для min
или max
,
новые значения могут быть пересчитаны из базовых таблиц с
учетом затронутых групп, когда кортеж, содержащий
текущие минимальные или максимальные значения, удаляется из базовой таблицы.
Поэтому обновление IMMV, содержащего
эти функции, может занять много времени.
Также обратите внимание, что использование sum
или
avg
для типа real
(float4
) или
double precision
(float8
)
в IMMV небезопасно, потому что агрегированные значения в IMMV могут
отличаться от результатов, рассчитанных из базовых таблиц, из-за
ограниченной точности этих типов. Чтобы избежать этой проблемы, используйте
тип numeric
.
F.46.8.1.1. Ограничения на агрегаты #
Если у нас есть GROUP BY
выражение, то выражения,
указанные в GROUP BY
, должны появляться в
целевом списке. Это то, как кортежи, которые будут обновлены в IMMV,
идентифицируются. Эти атрибуты используются в качестве ключей сканирования для
поиска кортежей в IMMV, поэтому индексы на них необходимы
для эффективного IVM.
Список целей не может содержать выражения, которые содержат агрегат в нем.
F.46.8.2. Подзапросы #
Простые подзапросы в FROM
и подзапросы с EXISTS в ‘WHERE’ поддерживаются.
F.46.8.2.1. Ограничения на подзапросы #
Подзапросы с использованием EXISTS и простые подзапросы в разделе FROM поддерживаются. Подзапросы EXISTS с условием, отличным от 'AND', и подзапросы в целевом списке не поддерживаются. Подзапрос EXISTS поддерживается только в WHERE, но не в целевом списке.
Если EXISTS содержит столбцы, которые ссылаются на столбцы в таблицах во внешнем запросе, такие столбцы должны быть включены в целевой список. Подзапросы, содержащие агрегатную функцию или DISTINCT
, не поддерживаются.
F.46.8.3. CTE #
Простые CTE (WITH
запросы) поддерживаются.
F.46.8.3.1. Ограничения на CTE #
WITH
запросы, содержащие агрегатную
функцию или DISTINCT
, не поддерживаются.
Рекурсивные запросы (WITH RECURSIVE
) не
разрешены. Нессылочные CTE также не разрешены, то есть
CTE должен быть упомянут хотя бы один раз в запросе определения представления.
F.46.8.4. DISTINCT #
DISTINCT
разрешен в определяющих запросах IMMV. Предположим, что IMMV определен с DISTINCT на базовой таблице, содержащей дублирующиеся кортежи. Когда кортежи удаляются из базовой таблицы, кортеж во представлении удаляется, если и только если кратность кортежа становится равной нулю. Более того, когда кортежи вставляются в базовую таблицу, кортеж вставляется в представление только в том случае, если такой же кортеж уже не существует в нем.
Физически, IMMV, определенный с DISTINCT
,
содержит кортежи после устранения дубликатов, и
кратность каждого кортежа хранится в дополнительной колонке с именем
__ivm_count__
, которая добавляется при создании такого IMMV.
F.46.8.5. TRUNCATE #
Когда базовая таблица усечена, IMMV также усечен, и
содержимое становится пустым, если запрос определения представления не
содержит агрегат без GROUP BY
клаузы. Агрегатные представления без GROUP BY
клаузы всегда имеют одну строку. Поэтому в таких случаях, если базовая
таблица усечена, IMMV просто обновляется вместо
усечения.
F.46.8.6. Одновременные транзакции #
Инкрементальные обновления представления в основном выполняются последовательно, даже при выполнении параллельных транзакций.
Предположим, что IMMV определен на двух базовых таблицах, и каждая таблица модифицируется в разных параллельных транзакциях одновременно. В транзакции, которая была зафиксирована первой, IMMV может быть обновлен, учитывая только изменения, сделанные в этой транзакции. Однако, чтобы правильно обновить IMMV в транзакции, которая фиксируется позже, нам нужно учитывать изменения, сделанные в обеих транзакциях.
По этой причине ExclusiveLock
удерживается на
IMMV сразу после изменения базовой таблицы на уровне изоляции
READ COMMITTED
. Это гарантирует, что IMMV обновляется в последующей транзакции только после
того, как предыдущая транзакция была зафиксирована. На уровне изоляции
REPEATABLE READ
или
SERIALIZABLE
ошибка возникает немедленно, если не удается получить блокировку, так как изменения, внесенные
другими транзакциями, не видны на этих уровнях, и IMMV
не может быть обновлен корректно в таких ситуациях.
Однако, как исключение, если IMMV имеет только одну базовую таблицу,
не использует DISTINCT или GROUP BY, и модифицируется с помощью
INSERT
, то блокировка, удерживаемая на IMMV, является
RowExclusiveLock
.
Даже если мы можем получить блокировку, параллельная транзакция
могла уже инкрементно обновить и зафиксировать представление
до того, как мы сможем получить блокировку. На уровне изоляции
REPEATABLE READ
или
SERIALIZABLE
это может привести
к неконсистентному состоянию представления. Поэтому возникает ошибка,
чтобы предотвратить аномалии, когда эта ситуация обнаружена.
F.46.8.7. row-level security #
Если некоторые базовые таблицы имеют политику безопасности на уровне строк, строки, которые не видны владельцу материализованного представления, исключаются из результата. Кроме того, такие строки также исключаются, когда представления поддерживаются инкрементно. Однако, если новая политика определена или политики изменены после создания материализованного представления, новая политика не будет применена к содержимому представления. Чтобы применить новую политику, необходимо воссоздать IMMV.
F.46.8.8. Как отключить или включить немедленное обслуживание #
IVM эффективен, когда мы хотим поддерживать IMMV в актуальном состоянии, и небольшая часть базовой таблицы изменяется нечасто. Из-за накладных расходов на немедленное обслуживание, IVM неэффективен, когда базовая таблица изменяется часто. Также, когда большая часть базовой таблицы изменяется или в базовую таблицу вставляются большие объемы данных, IVM неэффективен, и стоимость обслуживания может быть выше, чем обновление с нуля.
В такой ситуации мы можем использовать функцию refresh_immv
с with_data = false
, чтобы отключить
немедленное обслуживание перед изменением базовой таблицы. После
изменения базовой таблицы вызовите
refresh_immv
с
with_data = true
, чтобы обновить данные представления и
включить немедленное обслуживание.
F.46.9. Разработчик #
Группа разработки IVM
Оригинальные авторы
https://github.com/yugo-n
https://github.com/thoshiai
F.46.10. Лицензия #
F.46.11. Авторское право #
Частичный Copyright (c) 1996-2022, PostgreSQL Global Development Group
Частичный Copyright (c) 2022, IVM Development Group