postgresql的多列统计信息是用于优化多列组合查询场景的重要功能,当查询条件涉及同一张表的多个列时,默认的单列统计信息无法准确反映列之间的相关性,可能导致查询规划器生成不合适的执行计划。扩展统计信息(extended statistics)就是用来解决这个问题的特性。

多列统计信息的适用场景
当查询的WHERE条件同时包含多个列,且这些列之间存在相关性时,就需要使用多列统计信息。比如用户表中city和district两个列,同一个城市的区域数量是固定的,单列统计会认为两个列的取值是独立的,导致基数估算错误。
常见的适用场景包括:
- 多列等值查询条件组合
- 多列范围查询条件组合
- 多列参与JOIN或者GROUP BY的场景
创建多列统计信息
postgresql使用CREATE STATISTICS语句来创建扩展统计信息,语法如下:
-- 创建多列统计信息,指定统计类型为ndistinct和dependencies CREATE STATISTICS user_city_district_stats ON city, district FROM user_table WITH (ndistinct = true, dependencies = true);
上述语句中,ndistinct用于统计多列组合的不同值数量,dependencies用于统计列之间的函数依赖关系。如果需要统计多列的组合基数,还可以添加mcv选项,即最常见值统计。
更新多列统计信息
创建统计信息后,需要手动触发统计信息的更新,或者等待数据库自动的自动分析任务执行。手动更新的方式如下:
-- 对表执行ANALYZE,更新所有统计信息包括扩展统计 ANALYZE user_table;
如果只需要更新指定的扩展统计信息,可以在ANALYZE后指定统计信息名称:
-- 仅更新指定的扩展统计信息 ANALYZE user_table user_city_district_stats;
查看多列统计信息
可以通过系统表pg_statistic_ext和pg_statistic_ext_data来查看已创建的扩展统计信息。查询示例如下:
-- 查看扩展统计信息的基本信息
SELECT
s.stxname AS stats_name,
c.relname AS table_name,
array_agg(a.attname) AS columns,
s.stxkind AS stats_types
FROM pg_statistic_ext s
JOIN pg_class c ON s.stxrelid = c.oid
JOIN pg_attribute a ON a.attrelid = s.stxrelid AND a.attnum = ANY(s.stxkeys)
GROUP BY s.stxname, c.relname, s.stxkind;
如果要查看具体的统计内容,比如最常见值组合,可以使用pg_stats_ext视图:
-- 查看扩展统计的具体内容 SELECT * FROM pg_stats_ext WHERE stats_name = 'user_city_district_stats';
extstats解析
postgresql的扩展统计信息内部实现依赖extstats相关机制,核心结构存储在pg_statistic_ext系统表中,每个扩展统计对象会记录对应的表OID、包含的列编号、统计类型等信息。
不同的统计类型对应不同的存储内容:
| 统计类型 | 存储内容 | 作用 |
|---|---|---|
| ndistinct | 多列组合的不同值数量 | 估算多列组合的唯一值基数 |
| dependencies | 列之间的函数依赖关系 | 修正多列条件的选择率估算 |
| mcv | 多列组合的最常见值及频率 | 提升多列等值条件的基数估算精度 |
查询规划器在处理多列查询条件时,会先检查是否存在对应的扩展统计信息,如果存在则使用扩展统计的内容来修正选择率计算,从而生成更准确的执行计划。
使用注意事项
- 扩展统计信息不会自动创建,需要开发者根据查询场景手动创建
- 统计信息会占用额外的存储空间,不需要为所有多列组合都创建统计
- 表数据发生较大变化后,需要及时更新统计信息保证准确性
- 只有postgresql 10及以上版本支持扩展统计信息功能
示例验证
我们创建一个测试表,插入存在相关性的数据,对比使用多列统计信息前后的执行计划差异:
-- 创建测试表
CREATE TABLE test_order (
order_id int,
user_id int,
status int,
create_time timestamp
);
-- 插入测试数据,user_id和status存在相关性:user_id小于1000的用户status都是1
INSERT INTO test_order
SELECT
generate_series(1, 100000),
(random() * 2000)::int,
CASE WHEN (random() * 2000)::int < 1000 THEN 1 ELSE (random() * 5)::int + 1 END,
now() - (random() * 365 || ' days')::interval
;
-- 更新单列统计信息
ANALYZE test_order;
-- 查看未使用扩展统计时的查询计划
EXPLAIN SELECT * FROM test_order WHERE user_id < 1000 AND status = 1;
此时查询规划器会基于单列统计估算行数,结果会远大于实际行数。接下来创建扩展统计信息并更新:
-- 创建扩展统计信息 CREATE STATISTICS order_user_status_stats ON user_id, status FROM test_order WITH (dependencies = true, ndistinct = true); -- 更新统计信息 ANALYZE test_order; -- 再次查看查询计划 EXPLAIN SELECT * FROM test_order WHERE user_id < 1000 AND status = 1;
可以看到再次生成的执行计划中,估算的行数会接近实际行数,说明多列统计信息生效了。
postgresqlextended_statisticsmulticolumn_statsquery_planner修改时间:2026-06-17 12:39:48