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

时间:2015-04-03 18:22:32

标签: postgresql materialized-views

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

我该怎么做呢?

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

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

2 个答案:

答案 0 :(得分:84)

  

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

是的,PostgreSQL本身永远不会自动调用它,你需要以某种方式进行。

  

我该怎么做呢?

实现这一目标的很多方法。在给出一些示例之前,请记住REFRESH MATERIALIZED VIEW command确实在AccessExclusive模式下阻止了视图,因此在它工作时,您甚至无法在桌面上执行SELECT

但是,如果您使用的是9.4或更高版本,则可以为其指定CONCURRENTLY

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;

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

手动刷新

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

调度REFRESH操作

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

REFRESH

然后您的物化视图将在每30分钟刷新一次。

考虑

此选项非常好,特别是*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv" 选项,但前提是您始终可以接受100%最新的数据。请记住,即使有或没有CONCURRENTLYCONCURRENTLY命令也需要运行整个查询,因此在考虑调度时间之前,您必须花费时间运行内部查询。 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;
$$;

考虑

它在性能和并发性方面存在一些严重的缺陷:

  1. 任何INSERT / UPDATE / DELETE操作都必须执行查询(如果你考虑使用MV,这可能会很慢);
  2. 即使使用CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE ON table_name FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv(); ,一个CONCURRENTLY仍会阻止另一个REFRESH,因此所涉及的表上的任何INSERT / UPDATE / DELETE都将被序列化。
  3. 我认为唯一的情况是,如果这些变化真的很少见,那就是一个好主意。

    使用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秒内多次NOTIFYREFRESH将只触发一次。< / p>

    考虑

    作为cron选项,只有当你可以使用一些过时的数据时,这个选项才有用,但这样做的好处是REFRESH仅在真正需要时被调用,因此你的开销更少,并且此外,数据更新时更接近需要。

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

答案 1 :(得分:0)

让我在MatheusOl的前一个答案中指出三件事 - pglater技术。

  1. 作为long_options数组的最后一个元素,它应该包含&#34; {0,0,0,0}&#34;短语&#34指向https://linux.die.net/man/3/getopt_long的元素;数组的最后一个元素必须用零填充。&#34;所以,它应该读 -

    static struct option long_options[] =     {
          //......
          {"help", no_argument, NULL, '?'},
          {0, 0, 0, 0} 
    };
    
  2. 在malloc / free上 - 缺少一个免费(对于 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);
    
  3. 使用下表计算超时,其中md为mature_duration,即最新刷新(lr)时间点与当前时间之间的时差。

    当md&gt; = callback_delay(cd)==&gt;超时:0

    当md + PING_INTERVAL&gt; = cd ==&gt;超时:cd-md [= cd-(now-lr)]

    当md + PING_INTERVAL&lt; cd ==&gt;超时:PI

  4. 要实现此算法(第3点),您应该初始化&#39; lr&#39;如下 -

    res = PQexec(db, command);
    latest_refresh = time(0);
    if (PQresultStatus(res) == PGRES_COMMAND_OK) {
    
相关问题