在 PostgreSQL 中增量刷新物化视图

use*_*760 41 postgresql materialized-view postgresql-9.3 postgresql-9.4

是否可以在 PostgreSQL 中增量刷新物化视图,即仅针对新的或已更改的数据?

考虑这个表和物化视图:

CREATE TABLE graph (
   xaxis integer NOT NULL,
   value integer NOT NULL,
);

CREATE MATERIALIZED VIEW graph_avg AS 
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis
Run Code Online (Sandbox Code Playgroud)

定期添加新值graph或更新现有值。我想graph_avg每隔几个小时刷新一次视图,仅针对已更新的值。但是在 PostgreSQL 9.3 中,整个表都被刷新了。这是相当耗时的。下一个版本 9.4 允许CONCURRENT更新但它仍然刷新整个视图。有数以百万计的行,这需要几分钟。

跟踪更新和新值并仅部分刷新视图的好方法是什么?

Erw*_*ter 27

您始终可以实现自己的表作为“物化视图”。这就是我们之前MATERIALIZED VIEW在 Postgres 9.3 中实现的方式。

您可以创建一个普通的VIEW

CREATE VIEW graph_avg_view AS 
SELECT xaxis, AVG(value) AS avg_val
FROM   graph
GROUP  BY xaxis;
Run Code Online (Sandbox Code Playgroud)

并在您需要重新开始时实现一次或任何时候的结果:

CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view;
Run Code Online (Sandbox Code Playgroud)

(或者使用SELECT直接的语句,而无需创建一个VIEW。)
然后,根据您的使用情况未透露细节,你可以DELETE/ UPDATE/INSERT手动更改。

带有数据修改 CTE 的基本 DML 语句,如下所示

假设没有其他人试图写入graph_avg并行(阅读是没有问题的):

WITH del AS (
   DELETE FROM graph_avg t
   WHERE  NOT EXISTS (SELECT FROM graph_avg_view WHERE xaxis = t.xaxis)
   )
, upd AS (
   UPDATE graph_avg t
   SET    avg_val = v.avg_val
   FROM   graph_avg_view v
   WHERE  t.xaxis = v.xaxis
   AND    t.avg_val <> v.avg_val
-- AND    t.avg_val IS DISTINCT FROM v.avg_val  -- alt if avg_val can be NULL
   )
INSERT INTO graph_avg t  -- no target list, whole row
SELECT v.*
FROM   graph_avg_view v
WHERE  NOT EXISTS (SELECT FROM graph_avg WHERE xaxis = v.xaxis);
Run Code Online (Sandbox Code Playgroud)

基本配方

  • timestamp默认列添加now()到您的基表。让我们称之为ts
    • 如果您有更新,请添加触发器以设置当前时间戳,每次更新更改xaxisvalue
  • 创建一个小表来记住最新快照的时间戳。让我们称之为mv

    CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
    
    Run Code Online (Sandbox Code Playgroud)
  • 创建此部分多列索引:

    CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用上一个快照的时间戳作为查询中的谓词,以完美的索引使用刷新快照。

  • 在事务结束时,删除索引并使用事务时间戳替换索引谓词(最初'-infinity')中的时间戳重新创建它 ,您也将其保存到表中。一切都在一笔交易中。

  • 请注意,部分索引非常适合覆盖INSERTUPDATE操作,但不是DELETE. 为了解决这个问题,您需要考虑整个表格。这一切都取决于确切的要求。


Bas*_*que 19

并发更新(Postgres 9.4)

虽然不是您要求的增量更新,但 Postgres 9.4 确实提供了新的并发更新功能。

引用文档...

在 PostgreSQL 9.4 之前,刷新物化视图意味着锁定整个表,因此阻止任何查询它,并且如果刷新需要很长时间才能获取排他锁(当它等待使用它的查询完成时),它反过来正在阻止后续查询。现在可以使用 CONCURRENTLY 关键字来缓解这种情况:

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;
Run Code Online (Sandbox Code Playgroud)

但是,物化视图上需要存在唯一索引。它不是锁定物化视图,而是创建它的临时更新版本,比较两个版本,然后对物化视图应用 INSERT 和 DELETE 以应用差异。这意味着查询在更新时仍然可以使用物化视图。与它的非并发形式不同,元组不会被冻结,并且由于前面提到的 DELETE 会留下死元组,因此它需要 VACUUMing。

此并发更新仍在执行完整的新查询(不是增量查询)。因此 CONCURRENTLY 不会节省整体计算时间,它只会最大限度地减少物化视图在更新期间不可用的时间。

  • 有那么一刻,我很兴奋,直到我仔细阅读。`它改为创建它的临时更新版本......比较两个版本` - 这意味着临时更新版本仍然是一个完整的计算,然后它将差异应用于现有视图。所以基本上,我仍在重新进行所有计算,但只是在临时表中。 (15认同)
  • 啊,没错,`CONCURRENTLY` 并没有节省整体计算时间,它只是最大限度地减少了物化视图在更新期间不可用的时间。 (6认同)
  • 从 postgres 11 或 12 开始,这仍然是真的吗? (3认同)