Skip to content

物化视图使用场景 ​

物化视图(materialized view)用「空间换时间、新鲜度换速度」,把昂贵查询的结果落成物理表。它不是万能药:用错场景会带来陈旧数据、膨胀、刷新风暴。本节从典型场景出发,讲清什么样的查询适合物化、如何评估收益、如何组织刷新,以及何时应该改用别的方案。

前置知识

阅读本节前,建议先了解:物化视图概述、REFRESH MATERIALIZED VIEW、聚合函数

基础概念 ​

适合物化的查询特征 ​

text
一个查询是否适合做成物化视图,看五个维度:

  ① 昂贵     :执行一次很慢(大 JOIN、大聚合、跨库扫描)
  ② 高频     :被反复执行(每个用户打开页面都跑一次)
  ③ 结果小   :输出行数远小于扫描行数(汇总/去重后)
  ④ 可容忍延迟:数据晚几分钟/几小时无所谓
  ⑤ 相对稳定 :基表变化不会让整个结果作废

五条越靠前,收益越大;缺少「可容忍延迟」,就不该用物化视图。

收益来自哪里 ​

收益来源说明例子
少扫描不再扫基表,直接读预计算结果日销售汇总从 120 万行扫到 30 行
少计算聚合/JOIN/窗口只算一次sum、count(distinct)、排名
可索引物化视图能建索引,普通视图不能给汇总结果建唯一索引、GiST 索引
跨源隔离把远端/外部数据固化到本地postgres_fdw 拉取后本地查询
稳定计划免去复杂查询的计划抖动固定结果集,计划简单

不适合的场景 ​

场景为什么不合适替代方案
要求强实时刷新前数据必然过期普通视图 / 直接查询
基表几乎天天全量变每次刷新等于重算,收益低普通视图 + 索引优化
输出比输入还大扫描开销没省,还多占空间只存索引或分区表
需要按任意维度过滤预聚合维度固定,用不上汇总表 + 组合索引 / 列存扩展
需要增量更新PG 内核无增量维护第三方 pg_ivm 或自建位点
结果极小且查询极快优化索引即可,无需固化建索引

判定流程 ​

text
查询慢?
  │
  ├─ 否 → 不需要物化
  │
  └─ 是 → 查询高频被调用?
            │
            ├─ 否 → 优先优化索引/重写 SQL
            │
            └─ 是 → 能容忍延迟?
                      │
                      ├─ 否 → 换实时方案(缓存层 / 读副本 / 优化)
                      │
                      └─ 是 → 结果是否远小于扫描量?
                                │
                                ├─ 否 → 谨慎评估(可能只是"换个地方慢")
                                │
                                └─ 是 → ✅ 适合物化视图

语法与用法 ​

text
-- 1. 定义:把昂贵查询固化下来
CREATE MATERIALIZED VIEW mv_name AS
SELECT ... ;

-- 2. 建索引:让物化结果可被高效检索
CREATE [UNIQUE] INDEX idx_name ON mv_name (...);

-- 3. 首次填充 + 统计信息
REFRESH MATERIALIZED VIEW mv_name;
ANALYZE mv_name;

-- 4. 后续按需/定时刷新
REFRESH MATERIALIZED VIEW [CONCURRENTLY] mv_name;

最简示例——把「按地区汇总的销售」固化为物化视图:

sql
CREATE MATERIALIZED VIEW mv_region_sales AS
SELECT region_id,
       sum(amount)::numeric(14,2) AS sales_amt,
       count(*)                   AS order_cnt
FROM orders
GROUP BY region_id;

CREATE UNIQUE INDEX mv_region_sales_pk ON mv_region_sales (region_id);
ANALYZE mv_region_sales;

查询时像读普通表一样直接读它:

sql
SELECT region_id, sales_amt
FROM mv_region_sales
ORDER BY sales_amt DESC
LIMIT 5;
text
 region_id |  sales_amt
-----------+-------------
        03 |  9820314.55
        01 |  8411902.10
        07 |  7338200.44
        02 |  6190855.02
        09 |  5502114.77
(5 rows)

对比直接用基表聚合——两者结果一致,但代价差很多:

sql
-- 昂贵:每次都要扫描并聚合整张 orders
SELECT region_id, sum(amount)::numeric(14,2) AS sales_amt, count(*) AS order_cnt
FROM orders
GROUP BY region_id
ORDER BY sales_amt DESC
LIMIT 5;

详细说明 ​

场景一:报表与看板汇总 ​

最经典、收益最大的场景。看板通常只展示固定维度、固定时间粒度的汇总,且允许分钟级到小时级延迟。

sql
-- 小时级销售汇总(看板每次打开都查它)
CREATE MATERIALIZED VIEW mv_sales_hourly AS
SELECT date_trunc('hour', created_at) AS stat_hour,
       region_id,
       sum(amount)::numeric(14,2)  AS sales_amt,
       count(*)                    AS order_cnt,
       count(DISTINCT buyer_id)    AS buyer_cnt
FROM orders
WHERE created_at >= date_trunc('day', now()) - interval '30 days'
GROUP BY 1, 2;

CREATE UNIQUE INDEX mv_sales_hourly_pk
  ON mv_sales_hourly (stat_hour, region_id);
ANALYZE mv_sales_hourly;

收益量化——用一个真实的执行对比来说明(示意数据):

text
直接查 orders 聚合(近 30 天):
  Aggregate + Seq Scan,扫描约 1,240,000 行
  Execution Time: 1186.4 ms

查询 mv_sales_hourly:
  Index Scan / Seq Scan,读取约 21,600 行
  Execution Time: 12.7 ms

提速约 93 倍,且看板并发时不再挤压基表 IO。

关键点:给汇总结果加时间窗口(如 WHERE created_at >= now() - interval '30 days'),既控制结果规模,又能让旧数据随窗口滑出而不再重算。

场景二:跨库/外部数据本地化 ​

通过 postgres_fdw 等外部数据包装器(foreign data wrapper, FDW)访问远端数据时,网络往返与远端负载都不可控。把远端数据物化到本地,可显著降低查询延迟,还能建本地索引。

sql
-- 假设已配置好外部服务器与外部表 remote_orders
CREATE MATERIALIZED VIEW mv_remote_orders AS
SELECT order_id,
       buyer_id,
       amount,
       created_at
FROM remote_orders
WHERE created_at >= now() - interval '90 days';

-- FDW 的远端表无法建本地索引,物化视图可以
CREATE UNIQUE INDEX mv_remote_orders_pk ON mv_remote_orders (order_id);
CREATE INDEX mv_remote_orders_buyer ON mv_remote_orders (buyer_id, created_at);
ANALYZE mv_remote_orders;

对比效果(官方文档以 file_fdw 拼写检查为例,展示了同一种模式的巨大差异):

text
-- 直接扫外部表(每次都要读远端/文件)
Foreign Scan on remote_orders
  Rows Removed by Filter: 479829
  Execution Time: 188.273 ms

-- 走本地物化视图 + 索引
Index Only Scan using mv_remote_orders_pk on mv_remote_orders
  Execution Time: 0.117 ms

要点:本地物化视图可建索引,这是 FDW 外部表往往做不到的,因此这类场景收益通常比「省网络」本身还大。

场景三:复杂聚合与去重预计算 ​

count(DISTINCT ...)、多列去重、多维交叉汇总等操作非常昂贵。预计算后,日常查询只读结果。

sql
-- 每日各渠道的独立访客数(去重代价高)
CREATE MATERIALIZED VIEW mv_daily_uv AS
SELECT stat_date,
       channel,
       count(DISTINCT visitor_id) AS uv,
       count(DISTINCT session_id) AS pv_session
FROM visit_log
GROUP BY stat_date, channel;

CREATE UNIQUE INDEX mv_daily_uv_pk ON mv_daily_uv (stat_date, channel);
ANALYZE mv_daily_uv;

注意:去重类结果无法简单累加(不同天期的 UV 不能相加),所以刷新必须以「完整重算覆盖的目标区间」为单位,而不是只追加增量:

sql
-- 正确:定义查询覆盖要展示的完整区间,刷新时整体替换
-- 错误认知:把每天 UV 相加当作周 UV(结果偏大)
SELECT sum(uv) FROM mv_daily_uv WHERE stat_date >= date_trunc('week', now());  -- ❌ 语义错误

场景四:复杂 JOIN 的固化 ​

多层 JOIN、大表关联小表、自关联等查询,计划复杂且易抖动。物化后查询变成简单扫描。

sql
CREATE MATERIALIZED VIEW mv_order_detail_wide AS
SELECT o.order_id,
       o.created_at,
       b.buyer_name,
       p.product_name,
       c.category_name,
       oi.qty,
       oi.price,
       (oi.qty * oi.price)::numeric(14,2) AS line_amount
FROM orders o
JOIN buyers b       ON b.buyer_id = o.buyer_id
JOIN order_item oi  ON oi.order_id = o.order_id
JOIN products p     ON p.product_id = oi.product_id
JOIN categories c   ON c.category_id = p.category_id;

CREATE UNIQUE INDEX mv_order_detail_wide_pk ON mv_order_detail_wide (order_id);
CREATE INDEX mv_order_detail_wide_cat ON mv_order_detail_wide (category_name, created_at);
ANALYZE mv_order_detail_wide;

选型提醒:宽表物化的膨胀风险高(JOIN 会放大行数)。若输出行数远超单表,先考虑分区表或列存扩展,而不是盲目物化。

场景五:搜索与模糊匹配缓存 ​

模糊匹配、全文本、相似度排序等场景中,物化视图还能配合扩展建立高效索引(例如 pg_trgm 的 GiST 索引),这在外部表上通常不可行。

sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE MATERIALIZED VIEW mv_product_dict AS
SELECT product_id, product_name FROM products;

CREATE UNIQUE INDEX mv_product_dict_pk ON mv_product_dict (product_id);
-- 在物化视图上建立三元组(trigram)GiST 索引,加速相似度查询
CREATE INDEX mv_product_dict_trgm
  ON mv_product_dict USING gist (product_name gist_trgm_ops);

VACUUM ANALYZE mv_product_dict;
sql
-- 相似度排序,走 GiST 索引
SELECT product_name
FROM mv_product_dict
ORDER BY product_name <-> '蓝牙耳机'
LIMIT 10;

场景六:作为「汇总表」替代手工 ETL ​

在没有物化视图时,团队常手写「定时任务 + 汇总表」。物化视图把这一切标准化:定义查询即口径,REFRESH 即调度,无需自建表结构与写入逻辑。

text
手工汇总表方案                     物化视图方案
────────────────────────────────────────────────────────
CREATE TABLE summary (...);        CREATE MATERIALIZED VIEW ... AS SELECT ...;
INSERT ... SELECT ... ON CONFLICT  REFRESH MATERIALIZED VIEW ...;
自己管定义变更(改表结构)          DROP + 重建,定义随查询走
自己管去重与幂等                   全量替换,天然幂等

对比结论:口径复杂、变更频繁时,物化视图更省心;口径需要按维度增量维护、或要跨库写入下游时,手工汇总表仍有价值。

场景七:多级物化(汇总的汇总) ​

大报表常见「明细 → 日汇总 → 月汇总」的分层结构。多级物化能逐层压缩数据量,但刷新必须自底向上。

sql
-- 第二级:基于第一级物化视图再做月度汇总
CREATE MATERIALIZED VIEW mv_sales_monthly AS
SELECT date_trunc('month', stat_hour) AS stat_month,
       region_id,
       sum(sales_amt)::numeric(16,2) AS sales_amt,
       sum(order_cnt)                AS order_cnt
FROM mv_sales_hourly
GROUP BY 1, 2;

CREATE UNIQUE INDEX mv_sales_monthly_pk
  ON mv_sales_monthly (stat_month, region_id);
ANALYZE mv_sales_monthly;

刷新顺序:先小时级,再月级。

sql
REFRESH MATERIALIZED VIEW mv_sales_hourly;
ANALYZE mv_sales_hourly;

REFRESH MATERIALIZED VIEW mv_sales_monthly;
ANALYZE mv_sales_monthly;

查询依赖关系,可辅助确定刷新顺序:

sql
SELECT dependent.relname AS depends_on
FROM pg_depend d
JOIN pg_rewrite r ON r.oid = d.objid
JOIN pg_class dependent ON dependent.oid = r.ev_class
WHERE d.refobjid = 'mv_sales_hourly'::regclass
  AND dependent.relkind = 'm';
text
  depends_on
---------------
 mv_sales_monthly
(1 row)

场景八:机器学习特征与离线物化 ​

在 PostgreSQL 内做特征工程时,物化视图是「特征快照」的天然载体:训练与推理读同一份稳定快照,避免每次重算。

sql
CREATE MATERIALIZED VIEW mv_user_features AS
SELECT u.user_id,
       count(o.order_id)                                        AS order_cnt_90d,
       coalesce(sum(o.amount), 0)::numeric(14,2)                AS gmv_90d,
       coalesce(avg(o.amount), 0)::numeric(14,2)                AS avg_order_amt,
       max(o.created_at)                                        AS last_order_at,
       now()                                                    AS snapshot_at
FROM users u
LEFT JOIN orders o
       ON o.buyer_id = u.user_id
      AND o.created_at >= now() - interval '90 days'
GROUP BY u.user_id;

CREATE UNIQUE INDEX mv_user_features_pk ON mv_user_features (user_id);
ANALYZE mv_user_features;

快照时间戳 snapshot_at 可用于标注特征版本,便于复现:

sql
SELECT snapshot_at, count(*) AS feature_rows FROM mv_user_features GROUP BY snapshot_at;
text
         snapshot_at          | feature_rows
------------------------------+--------------
 2026-09-28 03:00:01.882+08   |       184220
(1 row)

场景九:读写分离下的读侧加速 ​

主库承担写入时,读请求常被挤到只读副本(read replica)。副本上做物化视图,可把重查询从主库彻底剥离。

sql
-- 在只读副本上创建物化视图(注意:只读副本需允许写入物化视图)
CREATE MATERIALIZED VIEW mv_heavy_report AS
SELECT ... FROM big_join ...;

CREATE UNIQUE INDEX mv_heavy_report_pk ON mv_heavy_report (...);

注意事项:

  • 逻辑复制/物理复制下,物化视图本身也会被复制或需在副本上重建,行为取决于复制方式,需按实际拓扑验证。
  • 只读事务中无法刷新,副本必须允许 REFRESH 写入物化视图。
  • 副本上的刷新同样会消耗副本资源,需与同步延迟一并评估。

场景十:反例——这些情况不要用物化视图 ​

text
❌ 强实时要求的账务余额
   刷新间隙读到旧值,可能造成对账差异。

❌ 输出行数接近或超过基表
   既没省扫描,又多占存储,还引入了刷新延迟。

❌ 需要按任意维度即时过滤
   预聚合维度固定,换个过滤条件就用不上。

❌ 基表每天全量重写
   每次刷新都等于全量重算,收益为零。

❌ 需要增量更新、逐条追踪变化
   PG 内核无 IVM,见「注意事项」。
sql
-- 反例:结果几乎与基表等大的"物化视图"
CREATE MATERIALIZED VIEW mv_orders_copy AS
SELECT order_id, buyer_id, amount, created_at, status, remark
FROM orders;   -- ❌ 只是复制了一张表,没有任何聚合收益

实战示例 ​

场景:某电商后台「经营看板」,需要展示近 30 天按地区、按日的销售汇总,允许 15 分钟延迟,页面 QPS 较高。

第一步:建立分层物化视图。

sql
-- 日 × 地区汇总
CREATE MATERIALIZED VIEW mv_sales_daily_region AS
SELECT stat_date,
       region_id,
       sum(amount)::numeric(16,2)  AS sales_amt,
       count(*)                    AS order_cnt,
       count(DISTINCT buyer_id)    AS buyer_cnt
FROM (
  SELECT date_trunc('day', created_at)::date AS stat_date,
         region_id, amount, buyer_id
  FROM orders
  WHERE created_at >= date_trunc('day', now()) - interval '60 days'
) t
GROUP BY stat_date, region_id;

CREATE UNIQUE INDEX mv_sales_daily_region_pk
  ON mv_sales_daily_region (stat_date, region_id);
CREATE INDEX mv_sales_daily_region_region
  ON mv_sales_daily_region (region_id, stat_date DESC);
ANALYZE mv_sales_daily_region;

第二步:看板查询改为直接读物化视图。

sql
SELECT stat_date,
       sum(sales_amt)::numeric(16,2) AS day_total
FROM mv_sales_daily_region
WHERE stat_date >= current_date - 30
GROUP BY stat_date
ORDER BY stat_date;
text
  stat_date  | day_total
-------------+------------
 2026-08-30  | 842113.20
 2026-08-31  | 903442.77
 2026-09-01  | 1187210.05
 ...
(30 rows)

第三步:建立刷新日志,便于观测与告警。

sql
CREATE TABLE mv_refresh_log (
  id           bigserial PRIMARY KEY,
  mv_name      text        NOT NULL,
  started_at   timestamptz NOT NULL,
  finished_at  timestamptz,
  duration_ms  integer,
  row_count    bigint,
  succeeded    boolean,
  err_message  text
);

CREATE OR REPLACE FUNCTION refresh_mv_logged(p_mv text, p_concurrently boolean DEFAULT true)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  v_start timestamptz := clock_timestamp();
  v_rows  bigint;
  v_id    bigint;
  v_sql   text;
BEGIN
  INSERT INTO mv_refresh_log (mv_name, started_at)
  VALUES (p_mv, v_start)
  RETURNING id INTO v_id;

  v_sql := format('REFRESH MATERIALIZED VIEW %s %I',
                  CASE WHEN p_concurrently THEN 'CONCURRENTLY' ELSE '' END, p_mv);

  BEGIN
    EXECUTE v_sql;
    EXECUTE format('ANALYZE %I', p_mv);
    EXECUTE format('SELECT count(*) FROM %I', p_mv) INTO v_rows;

    UPDATE mv_refresh_log
    SET finished_at = clock_timestamp(),
        duration_ms = (extract(epoch FROM clock_timestamp() - v_start) * 1000)::int,
        row_count   = v_rows,
        succeeded   = true
    WHERE id = v_id;
  EXCEPTION WHEN OTHERS THEN
    UPDATE mv_refresh_log
    SET finished_at = clock_timestamp(),
        duration_ms = (extract(epoch FROM clock_timestamp() - v_start) * 1000)::int,
        succeeded   = false,
        err_message = SQLERRM
    WHERE id = v_id;
    RAISE;
  END;
END;
$$;

第四步:定时刷新(每 15 分钟),由外部调度器触发。

bash
#!/usr/bin/env bash
# refresh_dashboard.sh —— 由 crontab 每 15 分钟调用
set -euo pipefail

PSQL="psql -X -q -v ON_ERROR_STOP=1 -d appdb"

# 低峰(02:00-05:00)用普通刷新更快,其余时段并发刷新不阻塞读
HOUR=$(date +%H)
if [ "$HOUR" -ge 2 ] && [ "$HOUR" -lt 5 ]; then
  $PSQL -c "SELECT refresh_mv_logged('mv_sales_daily_region', false);"
else
  $PSQL -c "SELECT refresh_mv_logged('mv_sales_daily_region', true);"
fi
text
# crontab -e
*/15 * * * * /opt/scripts/refresh_dashboard.sh >> /var/log/mv_refresh.log 2>&1

第五步:新鲜度与健康度监控。

sql
SELECT mv_name,
       max(finished_at)                                   AS last_success,
       now() - max(finished_at)                           AS staleness,
       round(avg(duration_ms))                            AS avg_ms,
       count(*) FILTER (WHERE NOT succeeded)              AS failures_24h
FROM mv_refresh_log
WHERE started_at > now() - interval '24 hours'
GROUP BY mv_name;
text
       mv_name         |         last_success         |  staleness   | avg_ms | failures_24h
-----------------------+------------------------------+--------------+--------+--------------
 mv_sales_daily_region | 2026-09-28 03:45:12.441+08   | 00:06:31.559 |    842 |            0
(1 row)

第六步:收益对比(示意)。

text
改造前(直接聚合 orders,30 天窗口)
  Parallel Seq Scan on orders: 扫描约 3,180,000 行
  HashAggregate
  Execution Time: 2,143.7 ms

改造后(读 mv_sales_daily_region)
  Index Scan / Bitmap Heap Scan: 读取约 480 行
  GroupAggregate
  Execution Time: 6.9 ms

提速约 310 倍;并发高峰时基表 IO 压力显著下降。

注意事项 ​

  • 错误!把物化视图当实时数据源。 刷新前必然过期,强实时业务读到旧值可能出问题。

    sql
    -- 不推荐:账务余额这种强实时数据用物化视图
    SELECT balance FROM mv_account_balance WHERE account_id = 1001;  -- 可能是旧的
  • 错误!认为 PostgreSQL 有内置增量刷新。 内核只有全量刷新(REFRESH 重跑定义查询),增量维护需第三方扩展或自建。

    sql
    -- 不推荐:以为只处理变化的数据
    REFRESH MATERIALIZED VIEW mv_huge;   -- 实际是全量重算
  • 错误!输出比输入还大却仍做物化。 既不省扫描,又增加存储和刷新成本。

    sql
    -- 不推荐:几乎等同复制基表
    CREATE MATERIALIZED VIEW mv_copy AS SELECT * FROM orders;
  • 错误!忘记刷新。 物化视图不会自动更新,没有调度就是一张「冻结的历史快照」。

    sql
    -- 不推荐:建完就不管
    CREATE MATERIALIZED VIEW mv_daily AS SELECT ...;   -- 之后从未 REFRESH
  • 不推荐:在只读事务中刷新。 刷新需要写入物化视图,只读事务里会失败。

    sql
    -- 不推荐
    BEGIN READ ONLY;
    REFRESH MATERIALIZED VIEW mv_daily;   -- 报错
    COMMIT;
  • 不推荐:不经评估就物化多张宽表。 宽表 JOIN 会放大行数,容易造成存储暴涨。

    sql
    -- 不推荐:五表 JOIN 结果上千万行,还物化
    CREATE MATERIALIZED VIEW mv_all_wide AS
    SELECT ... FROM a JOIN b JOIN c JOIN d JOIN e;
  • 不推荐:把去重结果直接相加。 count(DISTINCT ...) 的日值相加不等于区间值。

    sql
    -- 错误口径:日 UV 相加当周 UV
    SELECT sum(uv) FROM mv_daily_uv WHERE stat_date >= date_trunc('week', now());
  • 不推荐:多级物化却不按依赖顺序刷新。 先刷上层会导致上层基于旧的下层数据。

    sql
    -- 不推荐:先刷月级、后刷小时级
    REFRESH MATERIALIZED VIEW mv_sales_monthly;   -- ❌ 顺序颠倒
    REFRESH MATERIALIZED VIEW mv_sales_hourly;
  • 不推荐:物化视图不带时间窗口。 结果无限增长,刷新越来越慢。

    sql
    -- 不推荐:无限制历史
    CREATE MATERIALIZED VIEW mv_all_history AS SELECT ... FROM orders;  -- 永不收敛
  • 不推荐:不建索引。 物化视图的可索引优势没用上,查询仍是全表扫描。

    sql
    -- 不推荐:物化后不建任何索引
    CREATE MATERIALIZED VIEW mv_daily AS SELECT ...;   -- 查询全靠 Seq Scan

最佳实践 ​

  • 先量化收益再物化。 用 EXPLAIN (ANALYZE, BUFFERS) 对比直接查询与物化后的耗时,确认值得固化。

    sql
    EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS)
    SELECT region_id, sum(amount) FROM orders GROUP BY region_id;
  • 定义查询加上时间窗口,控制结果规模,让刷新随窗口滑出而稳定。

    sql
    WHERE created_at >= now() - interval '30 days'
  • 建唯一索引 + 按查询模式建辅助索引,兼顾并发刷新与检索效率。

    sql
    CREATE UNIQUE INDEX mv_pk ON mv_daily (stat_date, region_id);
    CREATE INDEX mv_region_idx ON mv_daily (region_id, stat_date DESC);
  • 刷新后补 ANALYZE,让优化器拿到最新统计信息。

    sql
    REFRESH MATERIALIZED VIEW mv_daily;
    ANALYZE mv_daily;
  • 刷新纳入监控与告警,记录耗时、行数、成功与否、新鲜度。

  • 多级物化严格自底向上刷新,并用依赖查询核对顺序。

  • 按业务紧迫度选择刷新频率,避免「一变就刷」。

  • 低频刷新 + 可容忍延迟的业务,优先普通刷新(简单、快、无索引前提);高频且读不能停时再用 CONCURRENTLY。

  • 对超大结果分片物化(按时间或维度拆成多个物化视图),降低单次刷新代价。

  • 定期检查膨胀,必要时 VACUUM/REINDEX。

    sql
    SELECT relname, n_live_tup, n_dead_tup, pg_size_pretty(pg_total_relation_size(relid)) AS size
    FROM pg_stat_user_tables
    WHERE relname LIKE 'mv_%'
    ORDER BY n_dead_tup DESC;
  • 把口径集中到少数几个物化视图,避免同一指标被多处重复计算导致不一致。

下一节

继续学习:CREATE RULE 规则

参考链接 ​