增量物化视图pg_ivm readme 中文版 翻译

pg_ivm

pg_ivm模块为PostgreSQL提供增量视图维护(IVM)功能。
该扩展兼容PostgreSQL 13、14、15、16和17版本。

描述

增量视图维护(Incremental View Maintenance, IVM) 是一种保持物化视图最新的方法,它仅计算和应用增量变化,而不是像REFRESH MATERIALIZED VIEW那样从头开始重新计算视图的内容。当视图中只有小部分发生变化时,IVM比重新计算更高效地更新物化视图。

关于视图维护的时机,有两种方法:即时维护和延迟维护。在即时维护中,视图在修改其基础表的同一事务中更新。在延迟维护中,视图在事务提交后更新,例如,当视图被访问时,响应如 REFRESH MATERIALIZED VIEW的用户命令,或在后台定期更新等。pg_ivm提供了一种即时维护方式,在基础表修改时,物化视图会在 AFTER 触发器中立即更新。

我们将支持IVM的物化视图称为增量可维护物化视图(Incrementally Maintainable Materialized View, IMMV)。要创建IMMV,您需要调用create_immv函数,并提供一个关系名和视图定义查询。例如:

SELECT create_immv('myview', 'SELECT * FROM mytab');

创建一个名为'myview'的IMMV,定义为SELECT * FROM mytab。这相当于以下命令,用于创建一个普通的物化视图:

CREATE MATERIALIZED VIEW myview AS SELECT * FROM mytab;

当IMMV被创建时,系统会自动创建一些触发器,以确保当基础表被修改时,视图的内容会立即更新。

postgres=# SELECT 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)

请注意,如果您使用的是PostgreSQL 17或更高版本,在IMMV的自动维护过程中,search_path会临时更改为pg_catalog, pg_temp

安装

要安装pg_ivm,请在模块目录中执行以下命令:

make install

如果您是通过rpm或deb安装PostgreSQL,您需要安装开发包(例如,postgresql14-devel 或 postgresql-server-dev-14)。

重要提示: 如果您希望在非默认或自定义构建的PostgreSQL上使用pg_ivm,不要忘记设置 PG_CONFIG 变量(make PG_CONFIG=...)或将 pg_config 命令的路径添加到 PATH 中。更多信息请参见此处

然后执行CREATE EXTENSION命令。

CREATE EXTENSION pg_ivm;

RPM包和yum源

pg_ivm的RPM包可以从PostgreSQL yum repository获取。有关详细信息,请参阅 instruction 。请注意,我们不是该yum仓库的维护者,仓库中的pg_ivm RPM包可能并不总是最新版本。

对象

安装pg_ivm后,以下对象将被创建:

函数

create_immv

使用create_immv函数来创建IMMV。

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的目标列表中包含其基础表的所有主键属性,则会在这些属性上创建唯一索引。在其他情况下,则不会创建索引。

refresh_immv

使用refresh_immv函数来更新IMMV。

refresh_immv(immv_name text, with_data bool) RETURNS bigint

refresh_immv完全替换IMMV的内容,类似于REFRESH MATERIALIZED VIEW命令对物化视图的作用。要执行此函数,您必须是IMMV的所有者(对于PostgreSQL 16或更早版本),或者在IMMV上拥有 MAINTAIN 权限(对于PostgreSQL 17或更高版本)。旧的内容将被丢弃。

with_data标志对应于REFRESH MATERIALIZED VIEW命令的WITH [NO] DATA选项。如果with_data为 true,则会执行基础查询以提供新的数据,如果IMMV为空,则会创建用于维护视图的触发器。此外,如果可能并且视图还没有唯一索引,则会为IMMV创建唯一索引。如果with_data为 false,则不会生成新数据,IMMV将变为空视图,且触发器将从IMMV中删除。请注意,尽管IMMV为空,但仍然可以扫描它。未来的行为可能会发生变化,当扫描一个空的IMMV时会抛出错误。

请注意,如果您使用的是PostgreSQL 17或更高版本,在执行refresh_immv时,search_path会临时更改为pg_catalog, pg_temp

get_immv_def

get_immv_def用于重建IMMV的基础 SELECT 命令。(这是一种反编译重建,并非命令的原始文本。)

get_immv_def(immv regclass) RETURNS text

IMMV metadata catalog

catalogpg_ivm_immv存储IMMV信息.

Name Type Description
immvrelid regclass The OID of the IMMV
viewdef text Query tree (in the form of a nodeToString() representation) for the view definition
ispopulated bool True if IMMV is currently populated

例子

一般来说,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)

另一方面,在使用如下相同的视图定义创建IMMV之后:

test=# SELECT 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)

为实现高效的IVM,在IMMV上建立合适的索引是很有必要的,因为我们需要在IMMV中查找要更新的元组。如果没有索引,那将会耗费很长时间。
因此,当通过create_immv函数创建IMMV时,若有可能会自动在其上创建一个唯一索引。如果视图定义查询包含GROUP BY子句,就会在GROUP BY表达式涉及的列上创建一个唯一索引。而且,如果视图有DISTINCT子句,会在目标列表的所有列上创建一个唯一索引。否则,如果IMMV在其目标列表中包含了基表的所有主键属性,就会在这些属性上创建一个唯一索引。在其他情况下,则不会创建索引。
在前面的示例中,会在immv的aid和bid列上创建一个名为immv_index的唯一索引,这使得视图能够快速更新。删除这个索引会使更新视图花费更长的时间。

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)

视图支持的定义和限制

目前,IMMV的视图定义可以包含内连接(inner joins)、DISTINCT 子句、一些内置聚合函数、FROM子句中的简单子查询、EXISTS 子查询以及简单的公共表达式CTE(即 WITH查询)。支持包括自连接(self-join)在内的内连接,但不支持外连接。支持的聚合函数有 count、sum、avg、min 和 max。其他聚合函数、包含聚合函数或 DISTINCT子句的子查询、FROM子句之外的子查询、窗口函数、HAVING子句、ORDER BY子句、LIMIT/OFFSETUNION/INTERSECT/EXCEPTDISTINCT ONTABLESAMPLEVALUES以及 FOR UPDATE/SHARE均不能在视图定义中使用。

基表必须是普通表。视图、物化视图、继承父表、分区表、分区以及外部表均不能使用。
任何系统列都不能包含在视图定义查询中。
目标列表中不能包含名称以__ivm_开头的列。
视图的目标列表中所使用的数据类型必须具有用于访问方法btree的默认操作符类。例如,jsonxmlpoint类型不能出现在目标列表中。
不支持逻辑复制,也就是说,即便发布者节点处的基表被修改了,基于这些基表定义的订阅者节点处的IMMV也不会更新。

注意事项

聚合函数

支持的聚合函数有count(计数)、sum(求和)、avg(求平均)、min(求最小值)以及 max(求最大值)。目前,仅支持内置的聚合函数,用户自定义的聚合函数无法使用。
当创建一个包含聚合函数的IMMV时,一些名称以__ivm开头的额外列会自动添加到目标列表中。__ivm_count__列包含了每个分组中聚合的元组数。此外,为了维护聚合值,会针对每个聚合值所在的列添加不止一个额外列。例如,会添加诸如__ivm_count_avg____ivm_sum_avg__这样的列来维护平均值。当基表被修改时,新的聚合值会利用旧的聚合值以及IMMV中存储的相关额外列的值来增量计算。
需要注意的是,对于min(求最小值)或max(求最大值)函数而言,当从基表中删除包含当前最小值或最大值的元组时,可能需要根据受影响的分组从基表中重新计算新的值。因此,更新包含这些函数的IMMV可能会耗费较长时间。
同样需要注意的是,在IMMV中对realfloat4)类型或double precisionfloat8)类型使用sum(求和)或avg(求平均)函数是不安全的,因为由于这些类型的精度有限,IMMV中的聚合值可能会与从基表计算得出的结果不一致。为避免这个问题,请改用numeric类型。

聚合函数的限制

如果有GROUP BY子句,在GROUP BY中指定的表达式必须出现在目标列表中。这是识别IMMV中要更新的元组的方式。这些属性被用作在IMMV中查找元组的扫描键,因此为实现高效的IVM,需要在它们上面创建索引。
目标列表不能包含其中含有聚合函数的表达式。

子查询

FROM子句中的简单子查询以及WHERE子句中的EXISTS子查询是受支持的。

子查询的限制

支持使用EXISTS的子查询以及FROM子句中的简单子查询。带有AND之外条件的EXISTS子查询以及目标列表中的子查询不受支持。EXISTS子查询仅在WHERE子句中受支持,在目标列表中则不受支持。
如果EXISTS包含引用了外层查询中表的列的列,那么这些列必须包含在目标列表中。
包含聚合函数或DISTINCT的子查询不受支持。

CTE

支持简单的CTE(WITH语句)。

CTE的限制

包含聚合函数或DISTINCTWITH查询不受支持。
递归查询(WITH RECURSIVE)是不被允许的。未被引用的公共表达式(CTE)也是不被允许的,也就是说,在视图定义查询中,一个公共表达式(CTE)必须至少被引用一次。

DISTINCT

在IMMV的定义查询中允许使用DISTINCT(去重)。假设基于一个包含重复元组的基表使用DISTINCT定义了一个IMMV。当从基表中删除元组时,当且仅当该元组的重复次数变为零时,视图中的相应元组才会被删除。而且,当向基表中插入元组时,只有当相同元组在视图中尚不存在时,才会将该元组插入到视图中。
从物理层面来讲,使用DISTINCT定义的IMMV在去除重复元组后包含相应元组,并且每个元组的重复次数会存储在一个名为__ivm_count__的额外列中,该列是在创建此类IMMV时添加的。

TRUNCATE

当对一个基表执行truncate操作时,如果视图定义查询不包含不带GROUP BY子句的聚合函数,那么IMMV也会被截断,其内容将变为空。不带GROUP BY子句的聚合视图始终只有一行数据。因此,在这类情况下,如果对基表进行截断操作,IMMV会直接进行刷新,而不是被截断。

并行事务

假设基于两个基表定义了一个IMMV,且每个表在不同的并发事务中同时被修改。在率先提交的事务中,IMMV可以仅考虑该事务中发生的变更来进行更新。另一方面,为了在稍后提交的事务中正确地更新IMMV,我们需要知晓两个事务中发生的变更情况。出于这个原因,在读已提交(READ COMMITTED)模式下,一旦基表被修改,就会立即对IMMV持有排他锁(ExclusiveLock),以确保在前一个事务提交后,IMMV能在后一个事务中得到更新。在可重复读(REPEATABLE READ)或可串行化(SERIALIZABLE)模式下,如果锁获取失败,会立即引发错误,因为在这些模式下,其他事务中发生的任何变更都是不可见的,在这种情况下IMMV无法被正确更新。不过,作为一种例外情况,如果IMMV只有一个基表,并且不使用DISTINCT或GROUP BY,且该表是通过INSERT语句进行修改的,那么对IMMV持有的锁则为行排他锁(RowExclusiveLock)。

行级安全

如果某些基表设有行级安全策略,那么对于物化视图所有者不可见的行将会从结果中排除。此外,在对视图进行增量维护时,此类行同样也会被排除。不过,如果在创建物化视图之后定义了新的策略或者对已有策略进行了更改,新策略将不会应用到视图内容上。要应用新策略,就需要重新创建IMMV。

如何启动即时维护

当我们希望IMMV保持最新状态,且基表只有一小部分不常被修改时,即时IVM是有效的。由于即时维护存在开销,当基表频繁被修改时,IVM就没那么有效了。而且,当基表的大部分内容被修改或者有大量数据插入到基表中时,即时IVM也没什么效果,并且其维护成本可能会比从头刷新视图的成本还要高。
在这种情况下,我们可以使用refresh_immv函数,并将with_data = false,以便在修改基表之前禁用即时维护。在对基表进行修改之后,再使用with_data = true来调用refresh_immv函数,从而刷新视图数据并启用即时维护。

作者

IVM开发小组

License

PostgreSQL License

Copyright

  • Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  • Portions Copyright (c) 2022, IVM Development Group
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 一星题 日志文件用来记录对数据库中数据进行的每一次更新操作。 索引是在基本表的列上建立的一种数据库对象,它同基本表...
    你不懂的阳阅读 1,053评论 0 1
  • PG内核分析 Q&A PG系统概述 为什么说PG是一种先进的对象—关系数据库系统 因为PG它不仅支持关系数据库的各...
    小蜜峰阅读 644评论 0 0
  • 概述 并行查询使用多个后台进程,但后端进程基本上处理连接的客户端发出的所有查询。改后端有五个子系统组成。 子系统功...
    奥利奥蘸墨水阅读 1,104评论 0 0
  • 前言 最近在搞520大促的事情,忙到脚不点地,所以就写些简单省事的吧。 物化视图概念 我们都知道,数据库中的视图(...
    LittleMagic阅读 28,882评论 14 62
  • 优化PG查询:一问一答 正文 Q1:是否有普罗米修斯exporter,你知道普罗米修斯监控PG的原生选项吗? 可以...
    yanzongshuaiDBA阅读 356评论 0 0