物化视图 · 把慢查询的结果冻起来

普通 VIEW 只是 SQL 别名,每次查都重算;MATERIALIZED VIEW 把结果物化到磁盘并可加索引,读取 = 直接读物理表,比反复重算快几个量级。代价是数据不实时,要 REFRESH——而 REFRESH CONCURRENTLY 能在不锁读的情况下刷新。

作者 David更新于 第 11 篇(共 17 篇)

物化视图 · 把慢查询的结果冻起来

有一类查询,算一次很贵(几张大表 JOIN + 聚合),结果却被反复读(BI 仪表盘每次打开都跑一遍)。每次重算,既慢又浪费。物化视图就是这个矛盾的解:算一次,把结果存下来,之后直接读存好的。

VIEW 与 MATERIALIZED VIEW:算不算存下来

先分清两个「视图」:

  • VIEW —— 只是给一段 SQL 起了个名字。每次查询视图,底层 SQL 重新执行一遍。它省的是「打字」,不省「计算」。
  • MATERIALIZED VIEW —— 把查询结果物化到磁盘,变成一张真实的物理表,还能加索引。之后查它 = 直接读这张表,完全不重算原始查询。

实测把一份 5000 行的销售数据聚合成日汇总:

-- 普通视图:每次查都现算这 719 行
CREATE VIEW v_daily_sales AS
  SELECT day, sum(amount) AS total FROM sales GROUP BY day;

-- 物化视图:719 行算一次,存进磁盘(实测占 64 kB)
CREATE MATERIALIZED VIEW mv_daily_sales AS
  SELECT day, sum(amount) AS total FROM sales GROUP BY day;

两者查询结果一样(都是 719 行),但物化视图读的是存好的结果,原始 5000 行的聚合一次都不用重跑。数据量从几千变成几千万时,这个差别就是「秒开」和「转圈十秒」的区别。

代价:数据不实时,要 REFRESH

天下没有免费的午餐。物化视图的结果是某一刻的快照,源数据后来变了,它不会自动更新:

往 sales 新增一行(2025-07-15, 99999)
→ 查 mv,那天的 total 仍是旧值(None,没刷新)
→ REFRESH MATERIALIZED VIEW 之后,才变成 99999

所以物化视图适合能容忍一点延迟的场景:日报、周报、排行榜、仪表盘——晚几分钟无所谓。刷新可以用定时任务(如 pg_cron)周期性跑。

REFRESH CONCURRENTLY:刷新时不锁读

普通 REFRESH MATERIALIZED VIEW 有个问题:刷新期间会锁表,读也被挡住。对一个「大家随时在看」的仪表盘,这很致命。REFRESH ... CONCURRENTLY 解决它:

-- 前提:物化视图上得有唯一索引
CREATE UNIQUE INDEX ON mv_daily_sales (day);

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_sales;  -- 刷新时照常能读

CONCURRENTLY 为什么需要唯一索引?因为它不是「清空重灌」,而是在后台把新结果算好,再和旧数据逐行比对,只更新有变化的行。要「逐行比对」,就必须能唯一标识每一行——这正是唯一索引的作用。代价是它比普通 REFRESH 慢(要算新结果 + 做 diff),但换来「刷新期间不阻塞读」,对在线服务几乎是必须的。

PostgreSQL 的短板:只能全量刷新

得诚实说一个短板:PostgreSQL 内置的物化视图只支持「全量刷新」——每次 REFRESH 都把整个查询重算一遍,哪怕源数据只改了一行。它没有 Oracle 那种「增量 / 快速刷新」(只算变化部分)。

如果你的物化视图很大、但每次源数据只变一点点,全量刷新会很浪费。解法有两个:① 用 pg_ivm 扩展做增量物化视图(Incremental View Maintenance,源表一变就增量更新);② 自己用「普通表 + 触发器 / 定时增量」手搓。选型时把这条记住:PG 原生物化视图 = 全量刷新,超大 + 高频更新的场景要另想办法。

横向对比

能力PostgreSQLMySQLOracleSQL Server
物化视图有❌ 无(自己拿表 + 触发器凑)有(功能最强)索引视图(indexed view)
并发刷新REFRESH CONCURRENTLY—有自动维护
增量刷新❌ 需 pg_ivm 扩展—有(fast refresh)自动增量维护
查询自动改写❌ 无—有(query rewrite)有限

这里 PostgreSQL 不是最强的:Oracle 的物化视图有增量刷新 + 查询改写(优化器自动把命中的原始查询改写到物化视图上),更省心。SQL Server 的索引视图自动维护(但限制多)。MySQL 干脆没有,只能用表 + 触发器 / 定时任务模拟。PostgreSQL 处在中间:有、好用、但要手动刷新、且默认全量。

应用场景

  • BI 仪表盘 / 报表:复杂多表聚合预计算成物化视图,前端秒开;定时(pg_cron)每几分钟 REFRESH CONCURRENTLY。
  • 排行榜 / 榜单:热度、销量榜算一次存下,读高频、更新低频,天生适配。
  • 复杂 JOIN 中间结果:反复用到的多表连接结果物化掉,下游查询直接读。
  • 跨库 / 外部数据快照:通过 postgres_fdw 拉外部数据,物化成本地快照,减少远程往返。

小结与下一章

物化视图用「一点延迟」换「读取快几个量级」,是应对「贵查询被反复读」的经典手段。REFRESH CONCURRENTLY 让它能在线刷新不挡读。要清醒的是它的短板——原生只支持全量刷新,超大高频场景要靠 pg_ivm 或手搓增量。

分区、物化视图都是「让大表 / 慢查询变快」的手段,但前提是——你得先知道慢在哪。规模与性能的最后一章,教你用 EXPLAIN 读懂查询计划,这是排查慢查询的第一手段。👉 EXPLAIN:读懂 PostgreSQL 的执行计划。

参考文献

本页目录