物化视图刷新从 40 分钟到 4 分钟

举报
yd_264617019 发表于 2026/10/18 10:35:03 2026/10/18
【摘要】 给运营搭的日报物化视图,数据源是几张亿级表的多层聚合,REFRESHMATERIALIZEDVIEW全量重建要40分钟,业务要求9点前出数,5点开始刷新,窗口越缩越紧。优化后刷新4分钟,还支持了白天每小时的准实时更新。第一步优化是REFRESH的语法层:普通REFRESH会先清空再重建,期间查询被锁(准确说拿排他锁,读全堵)。改用CONCURRENTLY,PG只增量比对差异行更新,查询不阻塞...

给运营搭的日报物化视图,数据源是几张亿级表的多层聚合,REFRESH MATERIALIZED VIEW 全量重建要 40 分钟,业务要求 9 点前出数,5 点开始刷新,窗口越缩越紧。优化后刷新 4 分钟,还支持了白天每小时的准实时更新。

第一步优化是 REFRESH 的语法层:普通 REFRESH 会先清空再重建,期间查询被锁(准确说拿排他锁,读全堵)。改用 CONCURRENTLY,PG 只增量比对差异行更新,查询不阻塞。但 CONCURRENTLY 有个硬前提——物化视图必须有唯一索引,加上唯一索引之后还白捡一个收益:差异比对基于唯一键,有变化才写,死元组大幅减少。

-- 1. 唯一索引是 CONCURRENTLY 的门票
CREATE UNIQUE INDEX ON mv_daily_report (stat_date, shop_id);

-- 2. 刷新命令换形态
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_report;

CONCURRENTLY 让刷新不锁读,但没让它变快——40 分钟还在。第二步是砍计算量。原视图的 SELECT 里有三个痛点:对亿级订单表做了两次重复扫描(两个子查询各扫一遍)、按表达式函数分组导致索引失效、聚合里混着 percentile_disc 这种昂贵函数。改写方向:用 CASE WHEN 把两个子查询合并成一次扫描分组;分组键从 date_trunc('day', create_time + interval '8 hour') 改成预生成的北京时间列;percentile 拆到每小时的轻量子视图,最终汇总时加权。这一轮下来刷新 11 分钟。

第三步是把"全量算"改成"增量算"。视图底层按天分区,刷新时 where 子句只扫当天和前一天(跨天边界修正)的分区,历史分区数据不变不重算。这个改动依赖物化视图定义改成"分区基表加过滤"的写法,刷新脚本按天传参。至此刷新 4 分钟,每小时跑一次也没压力。

调度的工程化也补齐了:用 pg_cron 挂每小时任务,刷新前用 advisory lock 防重入,失败写告警。物化视图体系跑起来后更像一个轻量数仓,运营提数需求从找开发写 SQL 变成了看板选时间范围,双方都省了力气。

复盘时总结的顺序感很关键:先解决锁(CONCURRENTLY 加唯一索引),再解决扫描量(SQL 改写加分区裁剪),最后解决重复计算(增量策略)。反过来做容易一开始就陷入增量架构的复杂度里。物化视图是"用存储换计算"的经典工具,但换多少、怎么换,还是得看执行计划和分区的脸色。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。