使用 PostgreSQL 物化视图缓存查询结果

John Doe 三月 11, 2026

在 PostgreSQL 中,面对跨大表的复杂聚合、联表查询,即便完成索引优化、参数调优和 SQL 重写,高频执行仍会出现查询超时、数据库高负载的问题。物化视图通过将查询结果物理存储为磁盘快照,以可控的刷新成本换取极致的查询速度,是解决这类问题的高效方案。

image

下面以电商订单收入汇总的实际场景为例,简单介绍 PostgreSQL 物化视图的落地与关键操作。

我们来针对一个电商订单收入汇总的业务做优化,该场景需对四张大表做关联聚合:orders(1000 万行)、order_items(4000 万行)、products(50 万行)、customers(200 万行),核心需求是按产品分类、客户地区、周维度聚合订单收入,过滤指定租户 90 天内数据并按收入降序排序。该查询为 BI 仪表盘、数据导出的核心语句,每天执行 40 多次,原始执行耗时 28 秒,频繁触发工具超时,是数据库的性能瓶颈。

原始耗时查询

SELECT
    p.category, c.region, DATE_TRUNC('week', o.order_date) AS week,
    COUNT(DISTINCT o.order_id) AS order_count,
    SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.tenant_id = 'acme_corp' AND o.order_date >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY p.category, c.region, DATE_TRUNC('week', o.order_date)
ORDER BY total_revenue DESC;

创建物化视图结构

使用WITH NO DATA仅创建结构不填充数据,便于先建索引再批量填充,实现平滑上线:

CREATE MATERIALIZED VIEW mv_order_revenue_summary AS
SELECT
    o.tenant_id, p.category, c.region, DATE_TRUNC('week', o.order_date) AS week,
    COUNT(DISTINCT o.order_id) AS order_count,
    SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY o.tenant_id, p.category, c.region, DATE_TRUNC('week', o.order_date)
WITH NO DATA;

填充物化视图数据

首次刷新仅耗时 4.2 秒,该耗时为后续每次刷新的基准成本:

REFRESH MATERIALIZED VIEW mv_order_revenue_summary;

按读取模式创建索引

物化视图可像普通表一样建索引,索引设计需匹配查询的读取模式,而非基表结构,这是性能提升的核心:

-- 条件列索引(租户、时间)
CREATE INDEX idx_mv_revenue_tenant_week ON mv_order_revenue_summary(tenant_id, week);
-- 分组维度索引
CREATE INDEX idx_mv_revenue_category ON mv_order_revenue_summary(category);
CREATE INDEX idx_mv_revenue_region ON mv_order_revenue_summary(region);

改写查询语句

基于物化视图的查询无需再做联表和聚合,仅通过索引扫描获取结果,执行耗时降至 180 毫秒,性能提升超 150 倍:

SELECT category, region, week, order_count, total_revenue
FROM mv_order_revenue_summary
WHERE tenant_id = 'acme_corp' AND week >= CURRENT_DATE - INTERVAL '90 days'
ORDER BY total_revenue DESC;