物化视图¶
核心问题:物化视图解决了什么问题?它和普通视图有什么区别?
它解决了什么问题?¶
复杂的聚合查询(如月度销售报表)每次执行都需要扫描大量数据,耗时数秒甚至数分钟。物化视图将查询结果持久化存储,查询时直接读取预计算结果,速度极快。
生活类比:普通视图是"每次问都现场计算",物化视图是"提前算好存起来,问的时候直接给答案"。
普通视图 vs 物化视图¶
| 对比项 | 普通视图 | 物化视图 |
|---|---|---|
| 数据存储 | 不存储,每次查询执行原始 SQL | 存储在磁盘,查询极快 |
| 数据新鲜度 | 实时 | 需要手动或定时刷新 |
| 查询性能 | 取决于原始 SQL 复杂度 | 极快(直接读取预计算结果) |
| 适用场景 | 简化查询,数据实时性要求高 | 复杂聚合,数据实时性要求低 |
| 可建索引 | ❌ | ✅ 可以在物化视图上建索引 |
创建与使用¶
-- 创建物化视图(存储查询结果)
CREATE MATERIALIZED VIEW monthly_sales AS
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS total_sales,
COUNT(*) AS order_count
FROM orders
GROUP BY DATE_TRUNC('month', order_date);
-- ✅ 在物化视图上建索引,进一步加速查询
CREATE INDEX ON monthly_sales (month);
-- 查询物化视图(极快,直接读取预计算结果)
SELECT * FROM monthly_sales WHERE month = '2024-01-01';
刷新物化视图¶
-- 普通刷新(锁表,刷新期间不可查询)
REFRESH MATERIALIZED VIEW monthly_sales;
-- ✅ 并发刷新(不锁表,刷新期间仍可查询旧数据)
-- 前提:物化视图上必须有唯一索引
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales;
为什么并发刷新需要唯一索引:并发刷新时,PostgreSQL 需要对比新旧数据,找出变化的行进行增量更新,唯一索引用于标识每一行。
定时刷新(结合 pg_cron)¶
-- 使用 pg_cron 扩展定时刷新(每天凌晨2点刷新)
SELECT cron.schedule(
'refresh-monthly-sales',
'0 2 * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales'
);
适用场景¶
| 场景 | 说明 |
|---|---|
| 报表统计 | 月度/季度销售报表,每天刷新一次即可 |
| 数据大屏 | 实时性要求不高(5分钟级别),但查询要快 |
| 复杂聚合 | 多表 JOIN + GROUP BY,原始查询耗时数秒 |
| 搜索优化 | 预计算搜索结果,加速搜索接口 |
工作中的坑¶
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 刷新时业务查询被阻塞 | 使用了普通 REFRESH(锁表) |
改用 REFRESH CONCURRENTLY(需要唯一索引) |
| 并发刷新报错 | 物化视图上没有唯一索引 | 先建唯一索引再并发刷新 |
| 数据不一致 | 忘记刷新物化视图 | 配置定时任务自动刷新 |
面试高频问题¶
Q:物化视图和普通视图有什么区别?什么时候用物化视图?
普通视图不存储数据,每次查询都执行原始 SQL,数据实时;物化视图将查询结果持久化存储,查询极快,但需要手动或定时刷新。当查询复杂(多表聚合)、数据实时性要求不高(允许分钟级延迟)时,用物化视图。
Q:物化视图刷新时如何避免影响业务查询?
使用
REFRESH MATERIALIZED VIEW CONCURRENTLY(并发刷新),刷新期间仍可查询旧数据,不锁表。前提是物化视图上必须有唯一索引。