Skip to content

ANALYZE 统计更新 ​

ANALYZE 是 PostgreSQL 收集表统计信息(statistics)的命令。优化器并不扫描真实数据来决定执行计划,而是依赖 pg_statistic 中保存的抽样统计(如空值比例、不同值个数、最常见值、数据分布直方图)来估算每一步会返回多少行。统计信息一旦过期,估算就会失真,优化器可能选错索引、选错连接方式,最终表现为「SQL 没改,昨天很快今天很慢」。

本节聚焦「统计信息是什么、ANALYZE 怎么收集、什么时候该手动跑、如何为关键列提高精度」,并说明它与 EXPLAIN ANALYZE 的本质区别——前者是收集统计,后者是执行语句。

前置知识

阅读本节前,建议先了解:EXPLAIN 执行计划

基础概念 ​

概念术语说明
统计信息Statistics优化器用于估算行数与选择率的元数据,存于 pg_statistic
抽样SamplingANALYZE 随机读取部分数据页而非全表扫描
样本量Statistics Target每列默认采样 300 × 300 个值(受 default_statistics_target 影响)
空值比例null_frac该列中 NULL 所占比例,用于 NULL 相关条件的估算
不同值个数n_distinct该列不同值的数量;为负时表示占总行数的比例
最常见值MCV(Most Common Values)出现频率最高的若干值及其频率 most_common_freqs
直方图Histogram Bounds把剩余取值域等分为若干桶的边界数组
相关性correlation物理存储顺序与逻辑排序的一致程度,取值 -1 ~ 1
扩展统计Extended Statistics用 CREATE STATISTICS 建立的多列相关性统计
统计目标SET STATISTICS单列采样精度,默认 100,越大越准但越慢
自动分析Autovacuum Analyze后台自动触发的 ANALYZE,由 autovacuum 进程执行
表级分析ANALYZE tablename只分析指定表或指定列
库级分析ANALYZE(无参数)分析当前数据库所有表
分析进度pg_stat_progress_analyze查看正在进行的 ANALYZE 进度(PostgreSQL 13+)

理解统计信息的关键三点:

  • 它是估算的依据:EXPLAIN 输出的 rows=N 不是猜的,而是用统计信息算出来的。
  • 它是抽样的:ANALYZE 不读全表,所以统计本身也有误差,但对计划选择通常足够。
  • 它会过期:大量增删改后,统计信息与真实数据脱节,必须刷新。
sql
-- 查看某列是否已有统计信息
SELECT attname, null_frac, n_distinct, correlation
FROM pg_stats
WHERE schemaname = 'public' AND tablename = 'orders';
text
 attname    | null_frac | n_distinct | correlation
------------+-----------+------------+-------------
 id         |         0 |         -1 |           1
 order_no   |         0 |         -1 |           1
 product_id |         0 |         12 |     0.01428
 qty        |         0 |          5 |     -0.0312
 amount     |         0 |         -1 |     0.99871
 status     |         0 |          4 |     -0.1103
 created_at |         0 |         -1 |           1
(7 rows)

语法与用法 ​

sql
ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]

-- 常用选项(PostgreSQL 11+ 支持括号语法)
VERBOSE [ boolean ]        -- 输出分析过程与结果信息

-- 常用形式
ANALYZE;                              -- 分析当前库所有表
ANALYZE orders;                       -- 只分析一张表
ANALYZE orders (status);              -- 只分析指定列
ANALYZE VERBOSE products;             -- 输出采样细节
sql
-- 最小可运行示例:分析一张表并查看统计
ANALYZE orders;
SELECT relname, last_analyze, last_autoanalyze, n_mod_since_analyze
FROM pg_stat_all_tables
WHERE relname = 'orders';
sql
-- 提高关键列的统计精度(列存在时会立即按新目标重新收集)
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders (status);

详细说明 ​

统计信息的作用:优化器的「估算输入」 ​

优化器的全部工作都是回答一个问题:这一步大概有多少行? 它把估算结果代入代价模型,比较几种执行路径的总代价,取最小者。估算的源头就是统计信息。

sql
-- 同一条 SQL,统计信息不同,计划可能完全不同
EXPLAIN SELECT * FROM orders WHERE status = 'paid';

如果 status 只有 4 个值且分布均匀(各占 25%),表有 100 万行,优化器估算约 25 万行 —— 此时索引扫描反而更慢,会选顺序扫描;如果 status 高度倾斜(paid 占 98%,cancelled 占 0.1%),查 cancelled 时估算约 1000 行,就会走索引。

text
-- 统计信息准确时,估算贴近实际
 Index Scan using idx_orders_status on orders  (cost=0.43..48.12 rows=1003 width=36)
   Index Cond: (status = 'cancelled'::text)
(2 rows)

MCV 就是用来刻画这种倾斜的:优化器记住了「哪些值最常见、各占多少比例」,所以能对热门值与冷门值给出不同估算。

ANALYZE 的采样机制 ​

ANALYZE 不扫描全表,而是随机抽取若干个数据页(由 default_statistics_target 换算得出),统计这些页上的值分布。

sql
-- 默认统计目标为 100,对应约 30000 行的采样量
SHOW default_statistics_target;
text
 default_statistics_target
---------------------------
 100
(1 row)

采样规模的经验关系:

统计目标大致采样行数适用场景
100(默认)约 30000绝大多数普通列
500约 150000倾斜严重、参与过滤的列
1000约 300000大表上的关键过滤列、连接列

采样越大越准,但 ANALYZE 耗时与 pg_statistic 体积都会增长,所以只对真正影响计划的列提高目标值。

ANALYZE 取得的是 ShareUpdateExclusiveLock,这是一种不与读写冲突的弱锁:

text
-- 锁冲突关系(简化)
ShareUpdateExclusiveLock 与 ACCESS SHARE / ROW SHARE / ROW EXCLUSIVE 不冲突
即:运行 ANALYZE 时表依然可以正常查询与写入

这正是自动分析可以随时在后台进行的原因。

各项统计字段的含义 ​

sql
SELECT attname, null_frac, avg_width, n_distinct, correlation
FROM pg_stats
WHERE tablename = 'products';
text
  attname  | null_frac | avg_width | n_distinct | correlation
-----------+-----------+-----------+------------+-------------
 id        |         0 |         4 |         -1 |           1
 sku       |         0 |        14 |         -1 |           1
 name      |         0 |        22 |         -1 |     -0.0421
 category  |         0 |         9 |          6 |     0.73215
 price     |         0 |         6 |         -1 |     0.99873
 stock     |         0 |         4 |         48 |      0.1104
(6 rows)

逐项说明:

字段含义对计划的影响
null_fracNULL 占比IS NULL 的估算行数 = 总行数 × null_frac
avg_width平均字节宽度影响 width= 与排序、哈希的内存需求估算
n_distinct不同值个数等值条件估算行数 ≈ 总行数 / n_distinct
most_common_valsMCV 数组高频值的精确频率,避免均匀分布假设失真
most_common_freqs对应频率与 MCV 一一对应
histogram_bounds直方图边界范围条件(>、<、BETWEEN)的估算依据
correlation物理顺序相关性接近 ±1 时索引扫描代价大幅下降,倾向于走索引

n_distinct 出现负值是有意义的:

text
 n_distinct = -1   → 唯一列(每行都不同,如主键),等值条件估算 rows ≈ 1
 n_distinct = -0.5 → 不同值个数约为表行数的一半
 n_distinct = 6    → 固定 6 个不同值(如 category)

correlation 是很容易被忽视的一项。它表示「表中的物理行顺序」与「该列值的排序」之间的相关系数:

sql
-- correlation 接近 1 时,范围查询走索引代价极低
SELECT attname, correlation FROM pg_stats
WHERE tablename = 'orders' AND attname IN ('id', 'created_at', 'status');
text
  attname   | correlation
------------+-------------
 id         |           1
 created_at |           1
 status     |     -0.1103
(3 rows)

created_at 随插入时间递增,物理顺序与逻辑顺序一致,correlation = 1,所以 WHERE created_at >= '2024-01-01' 这类范围查询走索引时几乎不产生随机 I/O。而 status 是随机分布的,correlation 接近 0,同样的范围条件代价就高得多。

手动执行与自动分析 ​

手动执行:

sql
-- 分析单表
ANALYZE orders;

-- 查看分析前后的时间戳变化
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_all_tables
WHERE relname = 'orders';
text
 relname |          last_analyze          |       last_autoanalyze
---------+--------------------------------+----------------------------
 orders  | 2026-09-24 10:12:33.451+08     | 2026-09-24 09:40:11.208+08
(1 row)

last_analyze 记录手动 ANALYZE 时间,last_autoanalyze 记录自动分析时间,两者分离,便于判断统计信息到底是谁更新的。

自动分析的触发条件:

text
触发阈值 = autovacuum_analyze_threshold + autovacuum_analyze_scale_factor × 表当前行数
        = 50 + 0.1 × reltuples
sql
SHOW autovacuum_analyze_threshold;
SHOW autovacuum_analyze_scale_factor;
text
 autovacuum_analyze_threshold
------------------------------
 50
(1 row)

 autovacuum_analyze_scale_factor
--------------------------------
 0.1
(1 row)

举例:一张 100 万行的表,触发自动分析需要约 50 + 0.1 × 1000000 = 100050 行被修改。也就是说,只改了 5 万行不会触发自动分析 —— 这正是很多「批量导入后计划跑偏」问题的根源。

sql
-- 查看自上次分析以来被修改的行数
SELECT relname, n_live_tup, n_mod_since_analyze
FROM pg_stat_all_tables
WHERE relname = 'orders';
text
 relname | n_live_tup | n_mod_since_analyze
---------+------------+---------------------
 orders  |      12040 |                9821
(2 rows)

当 n_mod_since_analyze 逼近阈值时,就说明统计信息即将过期。

SET STATISTICS:提高单列精度 ​

SET STATISTICS 修改的是单列的采样目标,覆盖 default_statistics_target。

sql
ALTER TABLE products ALTER COLUMN price SET STATISTICS 1000;

-- 查看当前设置
SELECT attname, attstattarget
FROM pg_attribute
WHERE attrelid = 'products'::regclass AND attstattarget <> -1;
text
 attname | attstattarget
---------+---------------
 price   |          1000
(1 row)

attstattarget = -1 表示使用默认值。修改目标后必须重新 ANALYZE 才生效:

sql
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders;

判断该给哪一列提高目标值,可以对比估算与实际:

sql
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded';
text
 Index Scan using idx_orders_status on orders  (cost=0.43..1024.30 rows=250 width=36) (actual rows=1003 loops=1)
   Index Cond: (status = 'refunded'::text)
(2 rows)

估算 250、实际 1003 —— 偏差 4 倍,说明 status 的分布没被 MCV 准确刻画,提高统计目标即可改善。

扩展统计:解决列间相关性 ​

多列条件是估算的重灾区。优化器默认假设各列相互独立,把选择率直接相乘,但真实数据往往存在相关性。

sql
-- 城市与邮编高度相关,独立性假设会严重低估
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM users WHERE city = '杭州' AND province = '浙江';
text
 Seq Scan on users  (cost=0.00..1024.00 rows=12 width=88) (actual rows=5821 loops=1)
   Filter: (((city = '杭州'::text) AND (province = '浙江'::text)))
   Rows Removed by Filter: 94179
(3 rows)

估算 12 行、实际 5821 行,这就是典型的相关性低估。默认统计无法解决,必须用扩展统计:

sql
-- 建立多列相关性统计
CREATE STATISTICS users_city_province (dependencies, ndistinct)
ON city, province FROM users;

ANALYZE users;

-- 查看扩展统计对象
SELECT stxname, stxkeys, stxkind FROM pg_statistic_ext;
text
     stxname      | stxkeys |  stxkind
------------------+---------+------------
 users_city_province | 1 2  | {d,f}
(1 row)

三种扩展统计类型的作用:

类型名称解决的问题
dependencies函数依赖一列由另一列决定(城市 → 省份)
ndistinct多列不同值数GROUP BY a, b 不同组合数被高估
mcv多列 MCV多列组合的高频值频率被低估
sql
-- 表达式的扩展统计(PostgreSQL 14+)
CREATE STATISTICS orders_expr_ndistinct (ndistinct)
ON (date_trunc('day', created_at)), status FROM orders;

ANALYZE orders;

建立后再次查看计划,估算会明显贴近实际:

text
 Seq Scan on users  (cost=0.00..1024.00 rows=5640 width=88) (actual rows=5821 loops=1)
   Filter: (((city = '杭州'::text) AND (province = '浙江'::text)))
(2 rows)

统计信息与执行计划的关系 ​

把整个过程串起来看:

text
数据 → ANALYZE 抽样 → pg_statistic(统计信息)
                            ↓
用户 SQL → 优化器读取统计信息 → 估算每步行数 → 代入代价模型 → 选出计划
                            ↓
                      EXPLAIN 输出 rows=N(估算值)

因此排查计划问题的标准顺序是:

  1. 先看 EXPLAIN ANALYZE 中估算与实际是否差数量级;
  2. 差得多,先 ANALYZE 刷新统计再复测;
  3. 刷新后仍差得多,考虑提高 SET STATISTICS;
  4. 多列条件仍然错,考虑 CREATE STATISTICS。
sql
-- 一键查看哪些表的统计信息最陈旧
SELECT schemaname, relname, last_analyze, n_mod_since_analyze
FROM pg_stat_all_tables
WHERE schemaname = 'public'
ORDER BY n_mod_since_analyze DESC
LIMIT 10;

手动 vs 自动 vs EXPLAIN ANALYZE 辨析 ​

三个名字都含 ANALYZE,含义完全不同:

项目ANALYZE自动分析EXPLAIN ANALYZE
目的收集统计信息收集统计信息执行语句并输出实际指标
是否执行 SQL否(只读抽样)否是(会真实改数据)
是否写 pg_statistic是是否
锁级别ShareUpdateExclusiveLock同左视语句而定
典型用途导入数据后刷新无人值守维护分析单条 SQL 性能
sql
-- ANALYZE:只是更新统计,不改业务数据
ANALYZE orders;

-- EXPLAIN ANALYZE:会真正执行 SELECT,但 SELECT 无副作用
EXPLAIN ANALYZE SELECT * FROM orders;

-- 对 DML 使用必须包事务回滚
BEGIN;
EXPLAIN ANALYZE UPDATE orders SET status = 'paid' WHERE id = 1;
ROLLBACK;

实战示例 ​

以下示例统一使用 demo 库。

1. 准备数据 ​

sql
CREATE DATABASE demo;
\c demo

CREATE TABLE products (
  id       integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku      text NOT NULL UNIQUE,
  name     text NOT NULL,
  category text NOT NULL,
  price    numeric(10, 2) NOT NULL,
  stock    integer NOT NULL DEFAULT 0
);

CREATE TABLE orders (
  id         integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_no   text NOT NULL,
  product_id integer NOT NULL REFERENCES products (id),
  qty        integer NOT NULL,
  amount     numeric(12, 2) NOT NULL,
  status     text NOT NULL,
  created_at timestamp NOT NULL DEFAULT now()
);

2. 造数据:让分布产生倾斜 ​

sql
-- 商品:6 个分类,价格集中在 50 ~ 500
INSERT INTO products (sku, name, category, price, stock)
SELECT
  'SKU-' || lpad(g::text, 4, '0'),
  '商品 ' || g,
  (ARRAY['数码', '家居', '服饰', 'food', '美妆', '运动'])[1 + (g % 6)],
  round((50 + random() * 450)::numeric, 2),
  (random() * 500)::integer
FROM generate_series(1, 2000) AS g;

-- 订单:状态高度倾斜,paid 占绝大多数
INSERT INTO orders (order_no, product_id, qty, amount, status, created_at)
SELECT
  'ORD-' || lpad(g::text, 6, '0'),
  1 + (g % 2000),
  1 + (g % 5),
  round((50 + random() * 450)::numeric, 2),
  CASE
    WHEN g % 100 < 90 THEN 'paid'        -- 90%
    WHEN g % 100 < 96 THEN 'pending'     -- 6%
    WHEN g % 100 < 99 THEN 'cancelled'   -- 3%
    ELSE 'refunded'                      -- 1%
  END,
  now() - (g || ' minutes')::interval
FROM generate_series(1, 12000) AS g;

3. 分析前后对比 ​

sql
-- 先手动制造「统计过期」的场景
DELETE FROM orders WHERE status = 'refunded';

-- 查看统计信息的陈旧程度
SELECT relname, n_live_tup, n_mod_since_analyze, last_analyze
FROM pg_stat_all_tables
WHERE relname = 'orders';
text
 relname | n_live_tup | n_mod_since_analyze |         last_analyze
---------+------------+---------------------+-------------------------------
 orders  |      11880 |                 120 | 2026-09-24 10:00:02.114+08
(1 row)
sql
-- 此时 EXPLAIN 的估算仍按旧分布计算,明显偏大
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded';
text
 Index Scan using idx_orders_status on orders  (cost=0.43..62.10 rows=118 width=36) (actual rows=0 loops=1)
   Index Cond: (status = 'refunded'::text)
(2 rows)
sql
-- 刷新统计后,估算归零,计划也随之改变
ANALYZE orders;

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded';
text
 Index Scan using idx_orders_status on orders  (cost=0.43..8.35 rows=1 width=36) (actual rows=0 loops=1)
   Index Cond: (status = 'refunded'::text)
(2 rows)

4. 查看关键列的统计信息 ​

sql
ANALYZE products;

SELECT
  attname,
  null_frac,
  n_distinct,
  array_length(most_common_vals::text::text[], 1) AS mcv_count,
  correlation
FROM pg_stats
WHERE tablename = 'products'
ORDER BY attname;
text
  attname  | null_frac | n_distinct | mcv_count | correlation
-----------+-----------+------------+-----------+-------------
 category  |         0 |          6 |         6 |    -0.0113
 id        |         0 |         -1 |           |           1
 name      |         0 |         -1 |           |     0.0218
 price     |         0 |         -1 |           |     0.99871
 sku       |         0 |         -1 |           |           1
 stock     |         0 |        481 |       100 |     0.0132
(6 rows)

category 有 6 个不同值且全部进入 MCV(因为不同值数小于统计目标的 MCV 容量);stock 有 481 个不同值,只保留前 100 个高频值作为 MCV,其余靠直方图刻画。

5. 为倾斜列提高统计精度 ​

sql
-- 先看默认精度下的偏差
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'pending';
text
 Index Scan using idx_orders_status on orders  (cost=0.29..342.10 rows=560 width=36) (actual rows=712 loops=1)
   Index Cond: (status = 'pending'::text)
(2 rows)
sql
-- 提高目标值后重新分析
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
ANALYZE orders (status);

SELECT attname, attstattarget FROM pg_attribute
WHERE attrelid = 'orders'::regclass AND attname = 'status';
text
 attname | attstattarget
---------+---------------
 status  |          1000
(1 row)

6. 用扩展统计修正多列低估 ​

sql
-- 构造相关性场景:产品价格与分类强相关
CREATE TABLE price_bands AS
SELECT
  p.id,
  p.category,
  CASE WHEN p.category = '数码' THEN 'high' ELSE 'low' END AS band,
  p.price
FROM products p;

ANALYZE price_bands;

-- 默认独立性假设下的估算
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM price_bands WHERE category = '数码' AND band = 'high';
text
 Seq Scan on price_bands  (cost=0.00..45.50 rows=55 width=40) (actual rows=334 loops=1)
   Filter: (((category = '数码'::text) AND (band = 'high'::text)))
(2 rows)
sql
-- 建立扩展统计后重测
CREATE STATISTICS price_bands_dep (dependencies)
ON category, band FROM price_bands;

ANALYZE price_bands;

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM price_bands WHERE category = '数码' AND band = 'high';
text
 Seq Scan on price_bands  (cost=0.00..45.50 rows=340 width=40) (actual rows=334 loops=1)
   Filter: (((category = '数码'::text) AND (band = 'high'::text)))
(2 rows)

估算从 55 修正到 340,与实际的 334 基本一致。

注意事项 ​

  • ANALYZE 不会更新业务数据。 它只读抽样并写 pg_statistic,与 EXPLAIN ANALYZE 完全不同。
sql
-- 正确:只收集统计
ANALYZE orders;
-- 错误理解:以为这条会执行查询并给出实际行数
-- (它不会输出任何计划)
  • ANALYZE 是抽样而非全表扫描,统计信息本身有误差。 极端分布或小表上偏差可能较大,不能指望 100% 精准。

  • 大量导入数据后必须手动 ANALYZE。 自动分析阈值是 50 + 0.1 × 行数,导入 10 万行通常不足以触发。

sql
-- 批量导入后的固定动作
COPY orders FROM '/tmp/orders.csv' WITH (FORMAT csv, HEADER true);
ANALYZE orders;
  • ANALYZE 与 VACUUM 是两件事。 VACUUM 回收空间、更新可见性映射;ANALYZE 更新统计信息。旧版本的 VACUUM ANALYZE 只是把两者写在一起,并不是一个操作。
sql
-- 组合执行为两步
VACUUM orders;
ANALYZE orders;

-- 或一次性
VACUUM ANALYZE orders;
  • SET STATISTICS 只影响后续 ANALYZE,不会立即重算。 改完必须再跑一次 ANALYZE。
sql
-- 错误!只改设置不会生效
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;

-- 正确:改完重新分析
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
ANALYZE orders;
  • 统计目标不是越大越好。 1000 会让 pg_statistic 膨胀、ANALYZE 变慢,且收益递减。
sql
-- 不推荐:给所有列都设超高目标
ALTER TABLE orders ALTER COLUMN order_no SET STATISTICS 10000;

-- 推荐:只给真正参与过滤、且分布倾斜的列提高
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
  • n_distinct = -1 不等于「没有统计」。 负数表示「近似比例」或「唯一」,是正常取值,不要当成异常。

  • 扩展统计只在优化的「组合选择率」阶段生效。 单列条件仍使用单列统计,扩展统计不能替代 SET STATISTICS。

  • ANALYZE 无法在事务块中对临时表之外的场景回滚影响。 它写入的统计信息是持久的,即使包在 BEGIN ... ROLLBACK 中也通常会被保留。

sql
-- 错误!以为回滚能撤销统计更新
BEGIN;
ANALYZE orders;
ROLLBACK;   -- 统计信息仍已更新

最佳实践 ​

  1. 把「数据初始化 / 批量导入后执行 ANALYZE」写进发布脚本,这是零成本、收益最高的一条。
bash
# 数据导入脚本片段
psql -d demo -v ON_ERROR_STOP=1 -c "COPY orders FROM '/tmp/orders.csv' CSV HEADER"
psql -d demo -c "ANALYZE orders"
  1. 调优 SQL 前先刷新统计,避免把「统计过期」误判为「优化器不行」。
sql
ANALYZE orders;
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT * FROM orders WHERE status = 'pending';
  1. 只对关键列提高统计目标,优先选择:分布倾斜的枚举列、参与连接的列、参与范围过滤的列。
sql
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ALTER TABLE orders ALTER COLUMN product_id SET STATISTICS 500;
ANALYZE orders;
  1. 多列条件出现数量级低估时用 CREATE STATISTICS,不要盲目提高单列目标。
sql
CREATE STATISTICS orders_product_status (dependencies, ndistinct)
ON product_id, status FROM orders;
ANALYZE orders;
  1. 定期巡检统计信息的陈旧度,把 n_mod_since_analyze 与阈值一起看。
sql
SELECT
  relname,
  n_live_tup,
  n_mod_since_analyze,
  50 + 0.1 * n_live_tup AS analyze_threshold,
  last_autoanalyze
FROM pg_stat_all_tables
WHERE schemaname = 'public' AND n_live_tup > 0
ORDER BY n_mod_since_analyze DESC
LIMIT 10;
  1. 大表可适度降低自动分析阈值,让统计更新更及时。
sql
-- 单表级别调整
ALTER TABLE orders SET (autovacuum_analyze_scale_factor = 0.02);
  1. ANALYZE 期间不必停服,它只取 ShareUpdateExclusiveLock,与读写不冲突;但避免在同一表上叠加 VACUUM FULL。

  2. 用 pg_stat_progress_analyze 观察大表分析进度,避免误以为命令卡死。

sql
SELECT relid::regclass, phase, sample_blks_total, sample_blks_scanned
FROM pg_stat_progress_analyze;
  1. 把 ANALYZE 纳入变更流程:任何数据分布发生结构性变化的操作(回填、批量清洗、分区切换)之后都要分析。
sql
UPDATE orders SET status = 'cancelled' WHERE status = 'pending' AND created_at < now() - interval '30 days';
ANALYZE orders (status);   -- 分布已改变,必须刷新

下一节

继续学习:VACUUM 清理

参考链接 ​