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

Postgresql 如何确保物化视图始终是最新的?,postgresql,materialized-views,Postgresql,Materialized Views,我需要调用刷新物化视图,对涉及的表进行每次更改,对吗?我感到惊讶的是,在网上没有太多关于这方面的讨论 我该怎么做呢 我想答案的上半部分就是我想要的: 这有什么危险吗?如果更新视图失败,调用update、insert等的事务是否会回滚?(我想这就是我想要的) 我需要调用刷新物化视图,对涉及的表进行每次更改,对吗 是的,PostgreSQL本身永远不会自动调用它,您需要以某种方式进行调用 我该怎么做呢 实现这一点的方法很多。在给出一些示例之前,请记住在AccessExclusive模式下确实会阻止视

我需要调用
刷新物化视图
,对涉及的表进行每次更改,对吗?我感到惊讶的是,在网上没有太多关于这方面的讨论

我该怎么做呢

我想答案的上半部分就是我想要的:

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

我需要调用
刷新物化视图
,对涉及的表进行每次更改,对吗

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

我该怎么做呢

实现这一点的方法很多。在给出一些示例之前,请记住在AccessExclusive模式下确实会阻止视图,因此在视图工作时,您甚至不能在表上执行
SELECT

但是,如果您使用的是9.4版或更高版本,则可以同时为其提供
选项:

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
这将获得一个排他锁,并且不会阻止
SELECT
查询,但可能会有更大的开销(取决于更改的数据量,如果更改的行很少,则可能会更快)。尽管您仍然无法同时运行两个
REFRESH
命令

手动刷新 这是一个可以考虑的选项。特别是在数据加载或批量更新的情况下(例如,系统在长时间后仅加载大量信息/数据),通常在末尾有修改或处理数据的操作,因此您可以简单地在末尾包含
刷新
操作

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

*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
然后,您的物化视图将每30分钟刷新一次

考虑 此选项非常好,特别是同时使用
选项
时,但前提是您可以接受数据始终不是100%最新的。请记住,即使同时执行或不执行
操作
刷新
命令也需要运行整个查询,因此,在考虑安排
刷新
的时间之前,您必须花费运行内部查询所需的时间

用触发器刷新 另一个选项是在触发器函数中调用
刷新物化视图
,如下所示:

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();
考虑 它在性能和并发性方面存在一些关键缺陷:

  • 任何插入/更新/删除操作都必须执行查询(如果您正在考虑MV,这可能会很慢)
  • 即使同时使用
    ,一个
    刷新
    仍会阻止另一个,因此相关表上的任何插入/更新/删除都将被序列化
  • 唯一的情况是,我认为这是一个好主意,如果变化真的很少

    使用LISTEN/NOTIFY刷新 前一个选项的问题是它是同步的,并且在每个操作中都会带来很大的开销。为了改善这一点,您可以像以前一样使用触发器,但这只会调用:

    因此,您可以构建一个保持连接的应用程序,并使用它来确定调用
    刷新
    的需要。您可以用来测试这一点的一个不错的项目是,在这个项目中,您可以使用shell脚本执行
    LISTEN
    ,因此您可以将
    刷新计划为:

    pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
    
    或者使用
    pglater
    (也在
    pgsidekick
    中)确保您不经常调用
    REFRESH
    。例如,您可以使用以下触发器使其在1分钟(60秒)内刷新:

    因此,它不会在相隔不到60秒的时间内调用
    刷新
    ,而且如果在不到60秒的时间内多次调用
    通知
    ,则
    刷新
    只会触发一次

    考虑 作为cron选项,只有当您可以使用少量陈旧数据时,这个选项才是好的,但它的优点是只有在真正需要时才调用
    刷新
    ,因此您的开销更小,而且数据更新更接近需要时的数据


    OBS:我还没有真正尝试过这些代码和示例,因此如果有人发现错误、打字错误或尝试了它并成功(或失败),请让我知道。

    让我指出MatheusOl之前回答的三件事——pglater技术

  • 作为long_options数组的最后一个元素,它应该包括“{0,0,0,0}”元素,正如短语“数组的最后一个元素必须用零填充”所指出的-

  • 在malloc/free事件上——缺少一个free(对于char listen=malloc(…);)。不管怎样,malloc导致pglater进程在CentOS上崩溃(但在Ubuntu上没有——我不知道为什么)。因此,我建议使用char数组并将数组名分配给char指针(char和char**)。在进行转换时,需要强制进行类型转换(指针赋值)

  • 使用下表计算超时,其中md是最新刷新(lr)时间点和当前时间之间的时间差

    当md>=回调\u延迟(cd)==>超时时:0

    当md+PING_INTERVAL>=cd==>超时时:cd md[=cd-(现在为lr)]

    当md+PING_间隔超时时:PI

  • 要实现这个算法(第三点),您应该初始化'lr',如下所示-


    我不清楚你想要的答案是什么,你链接的帖子中还没有提到。你的情况在某些根本性的方面有所不同吗?1)答案没有满足提问者的要求2)我问的问题至少有些不同。因此,我不知道这个答案是否完全适用于我的问题。在Oracle中,您会使用“ON COMMIT FAST”
    pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
    
    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;
    $$;
    
    static struct option long_options[] =     {
          //......
          {"help", no_argument, NULL, '?'},
          {0, 0, 0, 0} 
    };
    
    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);
    
    res = PQexec(db, command);
    latest_refresh = time(0);
    if (PQresultStatus(res) == PGRES_COMMAND_OK) {