如何确保物化视图始终是最新的?

Joh*_*hir 43 postgresql materialized-views

我需要调用REFRESH MATERIALIZED VIEW所涉及的表的每次更改,对吧?我很惊讶在网上找不到这方面的讨论.

我应该怎么做呢?

我认为答案的上半部分是我正在寻找的:https://stackoverflow.com/a/23963969/168143

这有什么危险吗?如果更新视图失败,是否会回滚调用更新,插入等事务?(这是我想要的......我想)

Mat*_*sOl 116

我需要调用REFRESH MATERIALIZED VIEW所涉及的表的每次更改,对吧?

是的,PostgreSQL本身永远不会自动调用它,你需要以某种方式做到这一点.

我应该怎么做呢?

实现这一目标的方法很多.在给出一些示例之前,请记住该REFRESH MATERIALIZED VIEW命令会在AccessExclusive模式下阻止视图,因此在它工作时,您甚至无法SELECT在表上执行操作.

但是,如果您使用的是9.4或更高版本,则可以CONCURRENTLY选择:

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
Run Code Online (Sandbox Code Playgroud)

这将获得一个ExclusiveLock,并且不会阻止SELECT查询,但可能会有更大的开销(取决于更改的数据量,如果几行已更改,则可能更快).虽然您仍然无法同时运行两个REFRESH命令.

手动刷新

这是一个可以考虑的选择.特别是在数据加载或批量更新的情况下(例如,长时间仅加载大量信息/数据的系统),通常在最后进行操作以修改或处理数据,因此您可以简单地REFRESH在结束了.

调度REFRESH操作

第一个广泛使用的选项是使用一些调度系统来调用刷新,例如,您可以在cron作业中配置类似:

*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
Run Code Online (Sandbox Code Playgroud)

然后您的物化视图将在每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;
$$;
Run Code Online (Sandbox Code Playgroud)

然后,在任何涉及视图更改的表中,您执行以下操作:

CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
Run Code Online (Sandbox Code Playgroud)

注意事项

它对性能和并发性有一些严重的缺陷:

  1. 任何INSERT/UPDATE/DELETE操作都必须执行查询(如果你考虑使用MV,这可能会很慢);
  2. 即使有CONCURRENTLY,REFRESH仍然会阻止另一个,因此所涉及的表上的任何INSERT/UPDATE/DELETE都将被序列化.

我认为唯一的情况是,如果变化真的很少,那么这是一个好主意.

使用LISTEN/NOTIFY刷新

前一个选项的问题在于它是同步的,并且在每个操作上都会产生很大的开销.为了改善这种情况,您可以像以前一样使用触发器,但只调用一个NOTIFY操作:

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, 'my_mv';
    RETURN NULL;
END;
$$;
Run Code Online (Sandbox Code Playgroud)

因此,您可以构建一个保持连接并使用LISTEN操作来确定需要调用的应用程序REFRESH.可以用来测试这个的一个很好的项目是pgsidekick,这个项目可以使用shell脚本来完成LISTEN,所以你可以安排REFRESHas:

pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
Run Code Online (Sandbox Code Playgroud)

或者使用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;
$$;
Run Code Online (Sandbox Code Playgroud)

所以它不会REFRESH在相隔60秒的时间内打电话,而且如果你NOTIFY在不到60秒的时间内多次打电话,它REFRESH只会被触发一次.

注意事项

作为cron选项,只有当你可以使用一些过时的数据时才能使用它,但这样做的好处REFRESH是只在真正需要的时候调用它,这样你的开销就会减少,并且数据更新更接近于需要的时候.

OBS:我还没有真正尝试过代码和示例,所以如果有人发现错误,拼写错误或尝试它并且有效(或不工作),请告诉我.

  • @a_horse_with_no_name,我认为你错了,如果你使用`CONCURRENTLY`它`DELETE`旧/更改行和`INSERT`新/更改行,但它不会触及未更改的行.请参阅PG源代码中的[refresh_by_match_merge函数](http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/matview.c;hb=HEAD#l512 ).无论如何,它确实执行整个底层查询. (6认同)
  • 史诗般的回答!我发现更新每个操作对我来说不是一个选择。我希望将物化视图用作花哨的复杂索引。但它不符合程序员对数据库使用的其他期望。所以我想这就是为什么这个“自动更新”功能没有内置到该功能中的原因。 (2认同)
  • @MatheusOl a_horse_with_no_name 是正确的。并发使用时,整个物化视图会重新计算。然后 postgres 将使用物化视图上的唯一索引来查找哪些行发生了更改,并且仅根据重新计算的值更新这些行。这可以防止阻塞整个视图,但不会加快计算时间。 (2认同)

bun*_*cis 18

现在有一个 PostgreSQL 扩展来保持物化视图更新:pg_ivm

它仅计算并应用增量更改,而不是像以前那样完全重新计算内容REFRESH MATERIALIZED VIEW。它有两种方法,IMMEDIATE并且DEFERRED

  • 对于IMMEDIATE,视图在修改其基表的同一事务中更新。

  • 对于DEFERRED,视图在事务提交后更新。

1.0版本已于2022年4月28日发布。