在DB2数据库中,查询优化器决定走索引扫描还是表扫描、多表连接采用什么顺序,靠的就是统计信息。很多执行计划突然变差的案例,追根溯源都是列统计信息缺失或过时。DB2提供的列统计收集能力,配合opt_column_stats相关的统计选项,可以让优化器拿到更精确的列数据分布,从而显著改善复杂查询的性能。本文将从原理、配置和实战排查三个层面,详细介绍如何用好这套机制。

一、列统计信息为什么如此关键
优化器本质上是一个成本估算引擎。它并不知道数据真实的样子,只能依靠数据字典中记录的统计信息来推断每个谓词大概会过滤掉多少行。当统计信息只包含最基本的行数(NPAGES、CARD)时,优化器对列值的分布只能做均匀假设,也就是认为每个值出现的频率大致相同。一旦真实数据是倾斜的,比如某个状态字段的95%记录都是同一个值,这个假设就会彻底失效。
举个典型场景:一张订单表有1亿行,其中ORDER_STATUS列有四个值。查询条件是STATUS = '已完成' AND CHANNEL = 'APP',如果优化器单独估算每个谓词的选择率再相乘,会严重低估或高估返回行数。行数估算错了,后面的连接顺序、JOIN方式、内存分配全都跟着错,最终可能选择嵌套循环连接去驱动一张实际要返回几百万行的中间结果集,性能直接崩掉。
列统计信息正是为了解决这个问题而存在的。通过在RUNSTATS命令中指定ON COLUMNS或使用统计概要文件(statistics profile),可以收集列的频度分布(FREQUENCY)、分位数信息(QUANTILE)以及列组统计(COLUMN GROUP),让优化器的估算贴近真实。而opt_column_stats所代表的,正是围绕列统计收集的一组细粒度控制选项,它决定了收集哪些列、收集多深的分布、以及自动维护策略如何触发。
二、opt_column_stats的配置与使用方法
先看最基础的手动收集方式。通过RUNSTATS命令针对特定列收集详细统计,是最直接的手段。下面的语句为表SALES.ORDER的REGION列和STATUS列收集最常用的20个高频值及其频度:
-- 收集指定列的频度统计
RUNSTATS ON TABLE SALES."ORDER"
ON COLUMNS (
REGION WITH DISTRIBUTION NUM_FREQVALUES 20 NUM_QUANTILES 50,
STATUS WITH DISTRIBUTION NUM_FREQVALUES 20 NUM_QUANTILES 50
);
-- 收集列组统计,帮助优化器处理多列相关性
RUNSTATS ON TABLE SALES."ORDER"
ON COLUMNS (
(REGION, CHANNEL) WITH DISTRIBUTION NUM_FREQVALUES 30
);
这里有两个关键参数需要理解。NUM_FREQVALUES控制收集多少个最常出现的值,值越大对倾斜列的刻画越精细,但统计信息占用的目录表空间和收集耗时也会增加;NUM_QUANTILES控制分位数桶的数量,主要用于范围谓词(BETWEEN、大于小于)的估算。一般经验是:倾斜严重的列把NUM_FREQVALUES设到20以上,经常出现在范围查询条件里的列保留默认或适当提高分位数。
如果是分区表或者想要长期自动化维护,建议使用统计概要文件配合自动收集。设置SET PROFILE选项后,DB2会在后台按策略刷新统计,不需要人工频繁干预:
-- 建立带分布选项的统计概要文件
RUNSTATS ON TABLE SALES."ORDER"
WITH DISTRIBUTION NUM_FREQVALUES 25 NUM_QUANTILES 50
SET PROFILE ONLY;
-- 开启自动统计收集
UPDATE DB CFG FOR SALESDB USING AUTO_MAINT ON;
UPDATE DB CFG FOR SALESDB USING AUTO_TBL_MAINT ON;
UPDATE DB CFG FOR SALESDB USING AUTO_RUNSTATS ON;
-- 针对关键表启用扩展列统计的自动维护
CALL SYSPROC.ADMIN_SET_DBPARM('AUTO_STMT_STATS','YES');
需要特别提醒一点:列组统计的列组合必须与查询谓词实际出现的组合匹配才能发挥作用。比如查询总是同时过滤REGION和CHANNEL,那只收集单列统计意义不大,必须显式声明(REGION, CHANNEL)这样的列组。列组数量不宜贪多,每个列组都会增加RUNSTATS的开销,建议只针对出现在高频查询谓词中的组合建3到5个。
三、诊断统计缺失与执行计划异常
配置好收集策略之后,还要会验证效果。最常见的问题是统计信息已经过期,但没人发现。可以通过系统目录表直接检查某张表的列统计情况:
-- 查看列的基本统计与分布统计是否存在
SELECT TABSCHEMA, TABNAME, COLNAME,
COLCARD, NUMNULLS,
AVGCOLLEN, STATS_TIME
FROM SYSCAT.COLDIST CD
WHERE TABSCHEMA = 'SALES' AND TABNAME = 'ORDER';
-- 查看频度统计明细,TYPE = 'F' 表示频度值
SELECT COLVALUE, VALUENUM, FREQUENCY
FROM SYSCAT.COLDIST
WHERE TABSCHEMA = 'SALES'
AND TABNAME = 'ORDER'
AND COLNAME = 'STATUS'
AND TYPE = 'F'
ORDER BY FREQUENCY DESC FETCH FIRST 10 ROWS ONLY;
如果查询结果为空,说明这张表从来没有收集过分布统计,优化器一直在用均匀假设做估算。另外要关注STATS_TIME字段,如果统计时间早于最近一次大批量数据加载,说明统计已经过期,应该立即触发一次RUNSTATS。
第二个诊断手段是对比估算行数与真实行数。先用EXPLAIN生成执行计划:
-- 生成执行计划
EXPLAIN ALL SET QUERYNO = 100 FOR
SELECT * FROM SALES."ORDER"
WHERE STATUS = '已完成' AND REGION = '华东';
-- 查看优化器估算的基数
SELECT OPERATOR_ID, CARD,
TQ_ID, OPERATOR_TYPE
FROM EXPLAIN_OPERATOR
WHERE QUERYNO = 100;
-- 实际执行并对比真实返回行数
SELECT COUNT(*) FROM SALES."ORDER"
WHERE STATUS = '已完成' AND REGION = '华东';
当估算行数和真实行数相差一个数量级以上时,基本可以断定是统计信息问题。此时按照前面章节的方法补齐列组统计,再重新生成执行计划对比,通常能看到估算基数明显回归合理,连接方式也会随之修正。如果补齐统计后估算依然不准,可以进一步启用语句级统计收集(automatic statement statistics),让优化器在编译SQL时对关键表自动触发轻量级统计刷新。
四、维护策略与常见误区
统计信息管理不是一次性工作,而应该纳入日常运维节奏。推荐的组合是:全量详细统计放在业务低峰期定时执行,日常依赖自动收集机制覆盖增量变化;对核心大表采用采样率控制收集成本,例如SAMPLE 25 BERNOULLI可以在精度和耗时之间取得平衡。变更窗口执行大批量ETL之后,务必手动触发一次相关表的RUNSTATS,这是最容易遗漏也最容易引发事故的环节。
几个常见误区值得注意。第一,认为统计收集越频繁越好,实际上对超大表频繁全量收集会带来可观的IO和CPU消耗,应该按数据变化比例触发;第二,盲目给所有列加分布统计,目录表SYSCAT.COLDIST会急剧膨胀,优化器编译时间也会变长,只处理谓词列才是正确做法;第三,升级或迁移数据库后忘记检查统计概要文件是否保留,导致新环境统计策略丢失。把列统计当作一份需要持续维护的资产,配合opt_column_stats提供的细粒度选项,优化器才能持续输出稳定高效的执行计划。
修改时间:2026-09-13 01:46:41