32.2 物化视图

一句话

物化视图就是把查询结果提前算好存下来:读变快、写变贵、一致性变弱;关键决策只有两个——刷新方式(全量 / 增量 / CDC)与 陈旧窗口(能容忍多久)。

三种刷新方式

方式 做法 延迟 成本
全量重建 定时 TRUNCATE + INSERT SELECT 分钟–小时 高(每次全表扫)
增量刷新 按时间/版本增量聚合 秒–分钟 中
CDC/事件驱动 订阅变更即时更新视图 毫秒–秒 低(但需要幂等)

本机实测:构建 20 万事件的视图耗时

操作 耗时
全量构建视图(20 万事件折叠) 约 1.6ms(纯内存模型,仅示意量级)
增量查询(视图点查) 0.0004ms
全量扫日志查询 5.1ms

真实系统里视图构建会是分钟级(磁盘 IO + 索引维护),但比例关系不变:构建一次、查询万次,才值得物化。

代码:幂等的增量刷新

-- 增量刷新:只处理“上次位点之后”的变更,可重复执行
INSERT INTO daily_sales(dt, amount, cnt)
SELECT date(created_at), sum(amount), count(*)
FROM orders
WHERE id > (SELECT last_id FROM refresh_state WHERE name='daily_sales')
  AND status = 'paid'
GROUP BY date(created_at)
ON CONFLICT (dt) DO UPDATE
  SET amount = daily_sales.amount + EXCLUDED.amount,
      cnt    = daily_sales.cnt + EXCLUDED.cnt;

UPDATE refresh_state SET last_id = (SELECT max(id) FROM orders) WHERE name='daily_sales';

注意:“更新位点”与“写入视图”必须同一事务,否则崩溃会漏数据或重复累加。

实测:视图的 “陈旧窗口” 怎么量

用与 CQRS 同一组数据(模拟投影滞后),视图相对写侧的滞后:

指标 滞后条数 换算时间(1000 条/秒)
P50 117 0.12s
P99 211 0.21s
最大 256 0.26s

“视图多久更新一次” 不等于 “用户多久看到”:定时刷新 1 分钟,实际陈旧窗口是 1 分钟 + 构建耗时;CDC 刷新则取决于消费延迟。对外必须报 “数据截至 HH:MM”,而不是假装实时。

反模式

反模式 后果
物化视图当 “强一致读” 用 业务看到旧数据,投诉 “刚改的没生效”
全量重建不锁/不切换 重建期间查询结果为空或不一致
增量刷新不做幂等 重跑一次,指标翻倍
视图里放明细(等价于复制表) 存储翻倍,刷新成本爆炸
视图不记录刷新位点 崩溃后不知道从哪继续
多个视图口径不一致 报表与页面数字对不上,互相甩锅

| 刷新周期按“整点”设计却不看构建耗时 | 整点后 3 分钟仍是旧数据,用户以为出错 | | 视图无 “最后更新时间” 字段 | 无法判断数据新旧,排障靠猜 |

三条铁律

🔑 物化 = 用存储与一致性换查询速度;先算 “查询频率 × 单次成本” 是否值得。

🔑 刷新必须幂等 + 记位点,否则重跑即错。

🔑 视图口径要有唯一 owner,多视图必须共享同一套定义。

自检

Q1 物化视图与索引有什么本质区别?

▸ 答案:索引用额外空间换单次查询的 IO,但结果仍由原表数据决定(强一致);物化视图存的是预计算的结果,本身是一份冗余数据,所以有 “陈旧” 概念。前者解决 “定位慢”,后者解决 “聚合慢”。

Q2 为什么 “全量重建” 通常需要 “影子表 + 原子切换”?

▸ 答案:因为重建过程要 TRUNCATE 或长时间写入,期间视图不可用/不完整。用影子表构建完再 RENAME/SWAP,查询侧要么看到旧视图、要么看到新视图,不会看到 “半成品”。

Q3 增量刷新最容易犯的错是什么?

▸ 答案:位点与数据不一致:先更新位点后写数据 → 崩溃漏数据;先写数据后更新位点 → 重跑重复累加。正确做法是同一事务,或用 “幂等 upsert + 可重复计算” 的设计(如按天覆盖而不是累加)。

Q4 什么时候该用 CDC 而不是定时刷新?

▸ 答案:当 “陈旧窗口” 要求秒级、且数据量大到全量刷新不划算时。CDC 的代价是:需要处理乱序、重复、schema 变更,还要有 “全量重建” 的兜底能力(CDC 链路坏掉时)。

Q5 物化视图的 “陈旧” 怎么对用户表达?

▸ 答案:三种:显示更新时间(“数据截至 10:05”)、分级展示(列表用视图,详情走实时)、交互兜底(用户点 “刷新” 触发实时查询)。最糟的是假装实时,让用户以为看到的是最新。

Q6 报表与页面数字对不上,最先查什么?

▸ 答案:先查 口径与位点:两个视图是否按同一过滤条件(如是否含退款)、刷新位点是否不同、是否一个有去重一个没有。多数 “数据不一致” 不是 bug,而是口径不同却没人写成文档。

Q7 定时刷新与 CDC 刷新,成本差在哪?

▸ 答案:定时刷新是周期性全量/增量扫描,成本与 “数据量 × 频率” 成正比,且空闲期也在扫;CDC 是按变更触发,成本与 “变更量” 成正比,空闲时几乎为零。数据量大、变更稀疏时 CDC 更省;变更极频繁时 CDC 的乱序处理与幂等成本会上升。

故障复盘(典型模式,脱敏)

阶段 现象 判断 动作
周一 大屏 GMV 与财务报表差 3% 两套视图口径不同(含退款 vs 不含) 对齐口径
T+2h 对齐后仍差 0.3% 刷新位点不同 统一刷新时间
T+1d 差异归零,但大屏延迟 2 小时 全量重建耗时 改增量刷新
复盘 视图无 owner、无口径文档 治理缺失 统一口径定义 + 更新时间展示

根因:多视图各自定义 “口径”,且刷新方式不同。代价:管理层看到的数字不可信。根治:口径唯一 owner + 视图显示 “截至时间” + 增量刷新。

关联