如何确保物化视图始终是最新的?
Posted
技术标签:
【中文标题】如何确保物化视图始终是最新的?【英文标题】:How can I ensure that a materialized view is always up to date? 【发布时间】:2015-06-08 20:47:03 【问题描述】:每次更改所涉及的表时,我都需要调用REFRESH MATERIALIZED VIEW
,对吗?我很惊讶在网上找不到太多关于此的讨论。
我该怎么做呢?
我认为答案的上半部分是我正在寻找的:https://***.com/a/23963969/168143
这有什么危险吗?如果更新视图失败,调用更新、插入等的事务是否会被回滚? (这就是我想要的……我想)
【问题讨论】:
我不清楚您正在寻找的答案在您链接到的帖子中尚未涵盖。你的情况在某些方面有什么不同吗? 1) 答案不满足该问题的提问者 2) 我在问一个至少有些不同的问题。所以我不知道这个答案是否完全适用于我的问题。 在 Oracle 中,您将使用“ON COMMIT FAST REFRESH”和“CREATE MATERIALIZED VIEW LOG”。不知道PG有没有类似的东西。 【参考方案1】:每次对涉及的表进行更改时,我都需要调用
REFRESH MATERIALIZED VIEW
,对吗?
是的,PostgreSQL 本身永远不会自动调用它,你需要通过某种方式来调用它。
我该怎么做呢?
实现这一目标的方法很多。在给出一些示例之前,请记住 REFRESH MATERIALIZED VIEW
command 确实会在 AccessExclusive 模式下阻止视图,因此当它工作时,您甚至不能在桌子上执行 SELECT
。
不过,如果您使用的是 9.4 或更高版本,您可以为其指定 CONCURRENTLY
选项:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
这将获得一个 ExclusiveLock,并且不会阻塞 SELECT
查询,但可能会产生更大的开销(取决于更改的数据量,如果更改的行数很少,那么它可能会更快)。虽然你仍然不能同时运行两个REFRESH
命令。
手动刷新
这是一个可以考虑的选项。特别是在数据加载或批量更新的情况下(例如,在很长一段时间后只加载大量信息/数据的系统),通常会在最后进行操作来修改或处理数据,因此您可以简单地包含 @987654334 @操作在最后吧。
安排刷新操作
第一个也是广泛使用的选项是使用一些调度系统来调用刷新,例如,您可以在 cron 作业中配置类似:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
然后您的物化视图将每 30 分钟刷新一次。
注意事项
这个选项非常好,特别是CONCURRENTLY
选项,但前提是您可以接受并非始终 100% 更新的数据。请记住,即使有或没有CONCURRENTLY
,REFRESH
命令确实需要运行整个查询,因此在考虑安排REFRESH
的时间之前,您必须花时间运行内部查询。
使用触发器刷新
另一种选择是在触发函数中调用REFRESH MATERIALIZED VIEW
,如下所示:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
RETURN NULL;
END;
$$;
然后,在涉及视图更改的任何表中,您可以:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
注意事项
它在性能和并发方面存在一些关键缺陷:
-
任何 INSERT/UPDATE/DELETE 操作都必须执行查询(如果您考虑 MV,这可能会很慢);
即使使用
CONCURRENTLY
,一个REFRESH
仍会阻塞另一个,因此相关表上的任何INSERT/UPDATE/DELETE 都将被序列化。
我认为这是一个好主意的唯一情况是,如果更改真的很少。
使用 LISTEN/NOTIFY 刷新
前一个选项的问题在于它是同步的,并且在每个操作中都会产生很大的开销。为了改善这种情况,您可以像以前一样使用触发器,但这只会调用NOTIFY
operation:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, 'my_mv';
RETURN NULL;
END;
$$;
然后您可以构建一个保持连接并使用LISTEN
operation 来确定调用REFRESH
的应用程序。一个可以用来测试的好项目是pgsidekick,在这个项目中你可以使用shell脚本来做LISTEN
,所以你可以将REFRESH
安排为:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
或使用pglater
(也在pgsidekick
内部)确保您不会经常致电REFRESH
。例如,您可以使用以下触发器使其成为REFRESH
,但在 1 分钟(60 秒)内:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, '60 REFRESH MATERIALIZED VIEW CONCURRENLTY my_mv';
RETURN NULL;
END;
$$;
所以它不会在 60 秒内调用REFRESH
,而且如果你在 60 秒内多次NOTIFY
,REFRESH
只会被触发一次。
注意事项
作为 cron 选项,这个选项也只有在你可以裸露一些陈旧数据的情况下才有用,但这样做的好处是只有在真正需要时才调用 REFRESH
,因此你的开销更少,而且数据更接近需要时更新。
OBS:我还没有真正尝试过代码和示例,所以如果有人发现错误、拼写错误或尝试过并且有效(或无效),请告诉我。
【讨论】:
史诗般的答案!我看到更新每个操作对我来说不是一个选择。我希望将物化视图用作花哨的复杂索引。但它不符合程序员对数据库使用的其他期望。所以我想这就是为什么这个“自动更新”功能没有内置到功能中的原因。 "如果更改的行数很少,那么它可能会更快" - 不会。 Postgres 总是重建整个 MVIEW 没有增量更新,所以 changed 行数不会影响刷新时间。 @a_horse_with_no_name,我认为你错了,如果你使用CONCURRENTLY
它 DELETE
old/changed rows 和 INSERT
new/changed rows,但它不会触及未更改的行.见refresh_by_match_merge function in PG's source code。在任何情况下,它都会执行整个底层查询。
@MatheusOl a_horse_with_no_name 是正确的。并发使用时会重新计算整个物化视图。然后 postgres 将使用物化视图上的唯一索引来查找哪些行已更改,并仅根据重新计算的值更新这些行。这可以防止阻塞整个视图,但不会加快计算时间。
@Wildcard 实体化视图确实允许索引,而CONCURRENTLY
实际上需要一个UNIQUE
索引:仅当实体化视图上至少有一个仅使用列名的唯一索引时才允许使用此选项并包括所有行;也就是说,它不能索引任何表达式,也不能包含 WHERE 子句。 postgresql.org/docs/current/static/…【参考方案2】:
让我指出关于 MatheusOl 上一个答案的三点 - pglater 技术。
作为 long_options 数组的最后一个元素,它应该包含“0, 0, 0, 0” 元素,正如短语“数组的最后一个元素必须用零填充”所指向的 https://linux.die.net/man/3/getopt_long。所以,它应该是 -
static struct option long_options[] = //...... "help", no_argument, NULL, '?', 0, 0, 0, 0 ;
关于 malloc/free 的事情——缺少一个 free(for char listen = malloc(...);)。无论如何,malloc 导致 pglater 进程在 CentOS 上崩溃(但在 Ubuntu 上没有——我不知道为什么)。因此,我建议使用 char 数组并将数组名称分配给 char 指针(同时指向 char 和 char**)。在执行此操作时,您需要强制进行类型转换(指针分配)。
char block4[100]; ... password_prompt = block4; ... char block1[500]; const char **keywords = (const char **)&block1; ... char block3[300]; char *listen = block3; sprintf(listen, "listen %s", id); PQfreemem(id); res = PQexec(db, listen);
使用下表计算超时时间,md 为mature_duration,即最近刷新(lr)时间点与当前时间的时间差。
当 md >= callback_delay(cd) ==> 超时:0
当 md + PING_INTERVAL >= cd ==> 超时:cd-md[=cd-(now-lr)]
当 md + PING_INTERVAL 超时:PI
要实现这个算法(第三点),你应该如下初始化'lr' -
res = PQexec(db, command); latest_refresh = time(0); if (PQresultStatus(res) == PGRES_COMMAND_OK)
【讨论】:
以上是关于如何确保物化视图始终是最新的?的主要内容,如果未能解决你的问题,请参考以下文章