ANALYZE 统计更新
ANALYZE 是 PostgreSQL 收集表统计信息(statistics)的命令。优化器并不扫描真实数据来决定执行计划,而是依赖 pg_statistic 中保存的抽样统计(如空值比例、不同值个数、最常见值、数据分布直方图)来估算每一步会返回多少行。统计信息一旦过期,估算就会失真,优化器可能选错索引、选错连接方式,最终表现为「SQL 没改,昨天很快今天很慢」。
本节聚焦「统计信息是什么、ANALYZE 怎么收集、什么时候该手动跑、如何为关键列提高精度」,并说明它与 EXPLAIN ANALYZE 的本质区别——前者是收集统计,后者是执行语句。
前置知识
阅读本节前,建议先了解:EXPLAIN 执行计划
基础概念
| 概念 | 术语 | 说明 |
|---|---|---|
| 统计信息 | Statistics | 优化器用于估算行数与选择率的元数据,存于 pg_statistic |
| 抽样 | Sampling | ANALYZE 随机读取部分数据页而非全表扫描 |
| 样本量 | 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不读全表,所以统计本身也有误差,但对计划选择通常足够。 - 它会过期:大量增删改后,统计信息与真实数据脱节,必须刷新。
-- 查看某列是否已有统计信息
SELECT attname, null_frac, n_distinct, correlation
FROM pg_stats
WHERE schemaname = 'public' AND tablename = 'orders'; 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)语法与用法
ANALYZE [ ( option [, ...] ) ] [ table_and_columns [, ...] ]
-- 常用选项(PostgreSQL 11+ 支持括号语法)
VERBOSE [ boolean ] -- 输出分析过程与结果信息
-- 常用形式
ANALYZE; -- 分析当前库所有表
ANALYZE orders; -- 只分析一张表
ANALYZE orders (status); -- 只分析指定列
ANALYZE VERBOSE products; -- 输出采样细节-- 最小可运行示例:分析一张表并查看统计
ANALYZE orders;
SELECT relname, last_analyze, last_autoanalyze, n_mod_since_analyze
FROM pg_stat_all_tables
WHERE relname = 'orders';-- 提高关键列的统计精度(列存在时会立即按新目标重新收集)
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders (status);详细说明
统计信息的作用:优化器的「估算输入」
优化器的全部工作都是回答一个问题:这一步大概有多少行? 它把估算结果代入代价模型,比较几种执行路径的总代价,取最小者。估算的源头就是统计信息。
-- 同一条 SQL,统计信息不同,计划可能完全不同
EXPLAIN SELECT * FROM orders WHERE status = 'paid';如果 status 只有 4 个值且分布均匀(各占 25%),表有 100 万行,优化器估算约 25 万行 —— 此时索引扫描反而更慢,会选顺序扫描;如果 status 高度倾斜(paid 占 98%,cancelled 占 0.1%),查 cancelled 时估算约 1000 行,就会走索引。
-- 统计信息准确时,估算贴近实际
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 换算得出),统计这些页上的值分布。
-- 默认统计目标为 100,对应约 30000 行的采样量
SHOW default_statistics_target; default_statistics_target
---------------------------
100
(1 row)采样规模的经验关系:
| 统计目标 | 大致采样行数 | 适用场景 |
|---|---|---|
| 100(默认) | 约 30000 | 绝大多数普通列 |
| 500 | 约 150000 | 倾斜严重、参与过滤的列 |
| 1000 | 约 300000 | 大表上的关键过滤列、连接列 |
采样越大越准,但 ANALYZE 耗时与 pg_statistic 体积都会增长,所以只对真正影响计划的列提高目标值。
ANALYZE 取得的是 ShareUpdateExclusiveLock,这是一种不与读写冲突的弱锁:
-- 锁冲突关系(简化)
ShareUpdateExclusiveLock 与 ACCESS SHARE / ROW SHARE / ROW EXCLUSIVE 不冲突
即:运行 ANALYZE 时表依然可以正常查询与写入这正是自动分析可以随时在后台进行的原因。
各项统计字段的含义
SELECT attname, null_frac, avg_width, n_distinct, correlation
FROM pg_stats
WHERE tablename = 'products'; 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_frac | NULL 占比 | IS NULL 的估算行数 = 总行数 × null_frac |
avg_width | 平均字节宽度 | 影响 width= 与排序、哈希的内存需求估算 |
n_distinct | 不同值个数 | 等值条件估算行数 ≈ 总行数 / n_distinct |
most_common_vals | MCV 数组 | 高频值的精确频率,避免均匀分布假设失真 |
most_common_freqs | 对应频率 | 与 MCV 一一对应 |
histogram_bounds | 直方图边界 | 范围条件(>、<、BETWEEN)的估算依据 |
correlation | 物理顺序相关性 | 接近 ±1 时索引扫描代价大幅下降,倾向于走索引 |
n_distinct 出现负值是有意义的:
n_distinct = -1 → 唯一列(每行都不同,如主键),等值条件估算 rows ≈ 1
n_distinct = -0.5 → 不同值个数约为表行数的一半
n_distinct = 6 → 固定 6 个不同值(如 category)correlation 是很容易被忽视的一项。它表示「表中的物理行顺序」与「该列值的排序」之间的相关系数:
-- correlation 接近 1 时,范围查询走索引代价极低
SELECT attname, correlation FROM pg_stats
WHERE tablename = 'orders' AND attname IN ('id', 'created_at', 'status'); 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,同样的范围条件代价就高得多。
手动执行与自动分析
手动执行:
-- 分析单表
ANALYZE orders;
-- 查看分析前后的时间戳变化
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_all_tables
WHERE relname = 'orders'; 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 记录自动分析时间,两者分离,便于判断统计信息到底是谁更新的。
自动分析的触发条件:
触发阈值 = autovacuum_analyze_threshold + autovacuum_analyze_scale_factor × 表当前行数
= 50 + 0.1 × reltuplesSHOW autovacuum_analyze_threshold;
SHOW autovacuum_analyze_scale_factor; autovacuum_analyze_threshold
------------------------------
50
(1 row)
autovacuum_analyze_scale_factor
--------------------------------
0.1
(1 row)举例:一张 100 万行的表,触发自动分析需要约 50 + 0.1 × 1000000 = 100050 行被修改。也就是说,只改了 5 万行不会触发自动分析 —— 这正是很多「批量导入后计划跑偏」问题的根源。
-- 查看自上次分析以来被修改的行数
SELECT relname, n_live_tup, n_mod_since_analyze
FROM pg_stat_all_tables
WHERE relname = 'orders'; relname | n_live_tup | n_mod_since_analyze
---------+------------+---------------------
orders | 12040 | 9821
(2 rows)当 n_mod_since_analyze 逼近阈值时,就说明统计信息即将过期。
SET STATISTICS:提高单列精度
SET STATISTICS 修改的是单列的采样目标,覆盖 default_statistics_target。
ALTER TABLE products ALTER COLUMN price SET STATISTICS 1000;
-- 查看当前设置
SELECT attname, attstattarget
FROM pg_attribute
WHERE attrelid = 'products'::regclass AND attstattarget <> -1; attname | attstattarget
---------+---------------
price | 1000
(1 row)attstattarget = -1 表示使用默认值。修改目标后必须重新 ANALYZE 才生效:
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders;判断该给哪一列提高目标值,可以对比估算与实际:
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded'; 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 准确刻画,提高统计目标即可改善。
扩展统计:解决列间相关性
多列条件是估算的重灾区。优化器默认假设各列相互独立,把选择率直接相乘,但真实数据往往存在相关性。
-- 城市与邮编高度相关,独立性假设会严重低估
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM users WHERE city = '杭州' AND province = '浙江'; 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 行,这就是典型的相关性低估。默认统计无法解决,必须用扩展统计:
-- 建立多列相关性统计
CREATE STATISTICS users_city_province (dependencies, ndistinct)
ON city, province FROM users;
ANALYZE users;
-- 查看扩展统计对象
SELECT stxname, stxkeys, stxkind FROM pg_statistic_ext; stxname | stxkeys | stxkind
------------------+---------+------------
users_city_province | 1 2 | {d,f}
(1 row)三种扩展统计类型的作用:
| 类型 | 名称 | 解决的问题 |
|---|---|---|
dependencies | 函数依赖 | 一列由另一列决定(城市 → 省份) |
ndistinct | 多列不同值数 | GROUP BY a, b 不同组合数被高估 |
mcv | 多列 MCV | 多列组合的高频值频率被低估 |
-- 表达式的扩展统计(PostgreSQL 14+)
CREATE STATISTICS orders_expr_ndistinct (ndistinct)
ON (date_trunc('day', created_at)), status FROM orders;
ANALYZE orders;建立后再次查看计划,估算会明显贴近实际:
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)统计信息与执行计划的关系
把整个过程串起来看:
数据 → ANALYZE 抽样 → pg_statistic(统计信息)
↓
用户 SQL → 优化器读取统计信息 → 估算每步行数 → 代入代价模型 → 选出计划
↓
EXPLAIN 输出 rows=N(估算值)因此排查计划问题的标准顺序是:
- 先看
EXPLAIN ANALYZE中估算与实际是否差数量级; - 差得多,先
ANALYZE刷新统计再复测; - 刷新后仍差得多,考虑提高
SET STATISTICS; - 多列条件仍然错,考虑
CREATE STATISTICS。
-- 一键查看哪些表的统计信息最陈旧
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 性能 |
-- 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. 准备数据
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. 造数据:让分布产生倾斜
-- 商品: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. 分析前后对比
-- 先手动制造「统计过期」的场景
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'; relname | n_live_tup | n_mod_since_analyze | last_analyze
---------+------------+---------------------+-------------------------------
orders | 11880 | 120 | 2026-09-24 10:00:02.114+08
(1 row)-- 此时 EXPLAIN 的估算仍按旧分布计算,明显偏大
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded'; 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)-- 刷新统计后,估算归零,计划也随之改变
ANALYZE orders;
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'refunded'; 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. 查看关键列的统计信息
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; 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. 为倾斜列提高统计精度
-- 先看默认精度下的偏差
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF)
SELECT * FROM orders WHERE status = 'pending'; 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)-- 提高目标值后重新分析
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'; attname | attstattarget
---------+---------------
status | 1000
(1 row)6. 用扩展统计修正多列低估
-- 构造相关性场景:产品价格与分类强相关
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'; 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)-- 建立扩展统计后重测
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'; 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完全不同。
-- 正确:只收集统计
ANALYZE orders;
-- 错误理解:以为这条会执行查询并给出实际行数
-- (它不会输出任何计划)ANALYZE是抽样而非全表扫描,统计信息本身有误差。 极端分布或小表上偏差可能较大,不能指望 100% 精准。大量导入数据后必须手动
ANALYZE。 自动分析阈值是50 + 0.1 × 行数,导入 10 万行通常不足以触发。
-- 批量导入后的固定动作
COPY orders FROM '/tmp/orders.csv' WITH (FORMAT csv, HEADER true);
ANALYZE orders;ANALYZE与VACUUM是两件事。VACUUM回收空间、更新可见性映射;ANALYZE更新统计信息。旧版本的VACUUM ANALYZE只是把两者写在一起,并不是一个操作。
-- 组合执行为两步
VACUUM orders;
ANALYZE orders;
-- 或一次性
VACUUM ANALYZE orders;SET STATISTICS只影响后续ANALYZE,不会立即重算。 改完必须再跑一次ANALYZE。
-- 错误!只改设置不会生效
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
-- 正确:改完重新分析
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
ANALYZE orders;- 统计目标不是越大越好。 1000 会让
pg_statistic膨胀、ANALYZE变慢,且收益递减。
-- 不推荐:给所有列都设超高目标
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中也通常会被保留。
-- 错误!以为回滚能撤销统计更新
BEGIN;
ANALYZE orders;
ROLLBACK; -- 统计信息仍已更新最佳实践
- 把「数据初始化 / 批量导入后执行
ANALYZE」写进发布脚本,这是零成本、收益最高的一条。
# 数据导入脚本片段
psql -d demo -v ON_ERROR_STOP=1 -c "COPY orders FROM '/tmp/orders.csv' CSV HEADER"
psql -d demo -c "ANALYZE orders"- 调优 SQL 前先刷新统计,避免把「统计过期」误判为「优化器不行」。
ANALYZE orders;
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT * FROM orders WHERE status = 'pending';- 只对关键列提高统计目标,优先选择:分布倾斜的枚举列、参与连接的列、参与范围过滤的列。
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ALTER TABLE orders ALTER COLUMN product_id SET STATISTICS 500;
ANALYZE orders;- 多列条件出现数量级低估时用
CREATE STATISTICS,不要盲目提高单列目标。
CREATE STATISTICS orders_product_status (dependencies, ndistinct)
ON product_id, status FROM orders;
ANALYZE orders;- 定期巡检统计信息的陈旧度,把
n_mod_since_analyze与阈值一起看。
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;- 大表可适度降低自动分析阈值,让统计更新更及时。
-- 单表级别调整
ALTER TABLE orders SET (autovacuum_analyze_scale_factor = 0.02);ANALYZE期间不必停服,它只取ShareUpdateExclusiveLock,与读写不冲突;但避免在同一表上叠加VACUUM FULL。用
pg_stat_progress_analyze观察大表分析进度,避免误以为命令卡死。
SELECT relid::regclass, phase, sample_blks_total, sample_blks_scanned
FROM pg_stat_progress_analyze;- 把
ANALYZE纳入变更流程:任何数据分布发生结构性变化的操作(回填、批量清洗、分区切换)之后都要分析。
UPDATE orders SET status = 'cancelled' WHERE status = 'pending' AND created_at < now() - interval '30 days';
ANALYZE orders (status); -- 分布已改变,必须刷新下一节
继续学习:VACUUM 清理