物化视图把查询结果存起来复用
物化视图保存定义查询的结果,让后续读取直接访问这些已存储的行。它把每次读取时的计算转移到刷新阶段,适合重复执行的报表查询。相应代价是明确的新鲜度边界,以及另一份需要维护的关系数据;它既不保证读取瞬间完成,也不会自动与源表保持同步。选择时应比较完整的读写成本。 已存储结果有自己的生命周期 普通视图保存查询定义,物化视图还保存查询结果。PostgreSQL 可以给物化结果建立索引,并直接针对它规划读取,而不必每次重跑原来的关联与聚合。源表发生普通插入或更新时,这份结果不会自动变化。应用也需要明确查询物化视图;创建它并不会让所有等价的源表查询自动改走这份缓存。 REFRESH MATERIALIZED VIEW 重新执行定义查询,再替换存储内容。标准 PostgreSQL 的刷新不是自动增量维护;即使源表只变了几行,定义查询仍然需要生成新的结果。比较物化与反复直接查询时,要把刷新计算、临时工作、存储和索引维护都计入,而不能只测读缓存的那条快速查询。 存储结果本身仍可能很大,或者缺少适合的索引,因此物化不代表任意读取都便宜。应根据报表过滤条件和返回字段,确定聚合粒度和索引。需要稳定顺序时,还应在读取查询中使用 ORDER BY;刷新不承诺保留定义查询所产生的物理排列,不能把上次看到的顺序当成输出契约。 并发刷新改变的是读者访问方式 普通刷新在替换物化内容时可能阻塞读取。CONCURRENTLY 允许 SELECT 在刷新期间继续访问已有结果,新内容何时可见仍遵循事务与快照规则。它依然需要锁、消耗资源,并可能等待其他操作;同一个物化视图在同一时刻也只能运行一个刷新任务,并不是允许任意多次刷新相互并行。 并发刷新要求视图已经有数据,并至少存在一个适用的唯一索引:索引只能使用列名,而且必须覆盖全部行。部分唯一索引或表达式索引都不满足这个条件。该索引用于刷新时识别结果行,并不意味着源表因此变得不可变,也不意味着源表在相同聚合粒度上具备同样的唯一性。 WITH NO DATA 会把视图变为未填充、不可扫描的状态。它不等于一个可以正常 SELECT 的空结果,也不会保留旧缓存答案。需要先通过普通刷新填充,再请求并发刷新。CONCURRENTLY 与 WITH NO DATA 不能组合成绕过这些前置条件的捷径,部署时应区分“零行有效结果”和“尚未生成结果”。 坏掉的样子 仅有刷新计划,并不能保证陈旧程度有上限。任务可能晚启动、运行很久、失败,或者排队等待另一个刷新;生成结果所用的源快照,也早于刷新工作完成的时刻。应记录成功刷新及报表所需的源数据覆盖范围,不能把配置了某个时间间隔当成数据已经及时更新的证据。失败任务不能像成功发布新结果一样推进页面的新鲜度标记。 部署预期使用并发读取路径时,缺少唯一索引或索引形态不符,会导致 CONCURRENTLY 直接失败。正式依赖前,应实际测试这条路径。PostgreSQL 十八的刷新还要求适用的 MAINTAIN 权限,所以定时任务所用角色必须确实能够刷新该关系。以所有者身份手工执行成功,不能证明任务角色也拥有所需权限。 快速验证 把下面内容保存为 materialized.sql,在可丢弃的 PostgreSQL 18 数据库中执行 psql -X -v ON_ERROR_STOP=1 -f materialized.sql。角色需要创建测试 schema 的权限。脚本在事务内创建 matview_demo,末尾整体回滚;如果这个名字已经存在,应选择未使用的名字,而不是删除已有 schema。 ...