在DB2数据库中,优化器生成执行计划时依赖表和列的统计信息。当某个列的值分布严重不均匀时,例如订单表中少量大客户占了绝大多数订单,而普通列统计信息只记录最小值、最大值和基数,优化器会假设数据均匀分布,从而低估或高估过滤条件的返回行数。这种误判可能导致全表扫描替代索引扫描,或者错误的表连接顺序。创建直方图的目的就是让优化器看到不同取值区间的真实频率,从而改善路径选择。

直方图在DB2优化器中的角色
DB2支持两种类型的直方图:频率直方图和分位数直方图。频率直方图适合重复值较多、不同取值数量有限的列,例如状态字段只有十几个值,它可以准确记录每个具体值出现的次数。分位数直方图则适合取值范围宽、重复值较少的数值列或时间列,它将数据按分位点划分成多个桶,每个桶代表一个数据区间及落入该区间的记录数。优化器在评估等值判断、范围扫描和连接条件时会参考这些桶的频率信息。
为什么基础统计不够用?举一个例子,假设customer_id有10000个不同值,但其中前10个大客户占了40%的订单。如果只收集列基数,优化器可能认为每个客户仅占0.01%的订单,于是使用customer_id等值条件时估计返回行数很少,并选择索引访问。实际上查询一个大客户可能返回几百万行,此时全表扫描反而更高效。直方图会告诉优化器这10个值占据了大量数据,从而修正估算结果。
在DB2中,直方图数据保存在SYSSTAT.COLDIST目录表中,而SYSSTAT.COLUMNS表包含NUM_QUANTILES等汇总信息。并不是所有列都需要直方图,只有那些出现在WHERE、JOIN、GROUP BY中且数据倾斜明显的列才值得收集,否则会增加目录表体积和统计信息收集时间。理解了这一点,就可以针对性地创建直方图。
使用RUNSTATS创建直方图
RUNSTATS是DB2收集统计信息的主要命令,创建直方图需要在命令中显式指定WITH DISTRIBUTION选项。如果不带该选项,默认只收集基础列统计信息,不会生成直方图。下面是一个基本示例,对orders表的order_status和customer_id两列生成分布统计,同时更新详细索引统计:
RUNSTATS ON TABLE myschema.orders WITH DISTRIBUTION ON COLUMNS(order_status, customer_id) AND DETAILED INDEXES ALL;
执行该命令后,DB2会根据两列的数据分布情况自动选择频率直方图或分位数直方图。如果希望更精细地控制直方图粒度,可以配合NUM_FREQVALUES和NUM_QUANTILES参数。例如,对于customer_id这种有数万个取值且倾斜明显的列,可以设置分位数桶数为100,频率值上限为20:
RUNSTATS ON TABLE myschema.orders WITH DISTRIBUTION ON COLUMNS(customer_id) NUM_QUANTILES 100 NUM_FREQVALUES 20;
需要注意的是,NUM_QUANTILES控制分位数直方图的桶数量,数值越大,优化器看到的数据分布越精细,但收集耗时也越长。NUM_FREQVALUES则控制频率直方图最多记录多少个高频值。对于大表,直方图收集可能消耗较多CPU和临时空间。此时可以使用采样方式降低开销,例如对orders表采样20%的数据来收集customer_id的直方图:
RUNSTATS ON TABLE myschema.orders WITH DISTRIBUTION ON COLUMNS(customer_id) TABLESAMPLE BERNOULLI(20) NUM_QUANTILES 50;
采样比例过低会导致直方图误差增大,通常不建议低于20%。对于频繁变更的表,直方图很容易过期,需要结合维护窗口或自动统计信息作业及时更新。
查看直方图统计信息
收集完成后需要验证直方图是否真正写入目录表。DB2将直方图数据存储在SYSSTAT.COLDIST表中,每一行代表一个直方图桶或一个频率值。通过查询该表可以查看特定列的分布桶序号、值以及出现次数。下面的SQL用于查看orders表customer_id列的直方图内容:
SELECT SEQNO, COLNAME, COLVALUE, VALCOUNT FROM SYSSTAT.COLDIST WHERE TABSCHEMA = 'MYSCHEMA' AND TABNAME = 'ORDERS' AND COLNAME = 'CUSTOMER_ID' ORDER BY SEQNO;
在SYSSTAT.COLDIST表中,COLVALUE字段根据数据类型存储具体值或分位边界,VALCOUNT表示该值的出现次数或该桶的累计频率。SEQNO表示桶序号,从1开始递增。对于频率直方图,每个不同值对应一行,VALCOUNT就是该值的出现次数。对于分位数直方图,每行对应一个分位桶的上界和落入该桶内的记录数。
还可以查询SYSSTAT.COLUMNS表来确认直方图参数是否更新。下面的SQL返回orders表中各列的直方图桶数量和频率值上限:
SELECT COLNAME, NUM_QUANTILES, NUM_FREQVALUES FROM SYSSTAT.COLUMNS WHERE TABSCHEMA = 'MYSCHEMA' AND TABNAME = 'ORDERS';
如果NUM_QUANTILES为0,说明该列没有分位数直方图;如果NUM_FREQVALUES为0,说明没有频率直方图。通过对比收集前后的访问计划,可以直观看到直方图对优化器估算行数的影响。如果估算行数接近实际返回行数,说明直方图准确性良好。
直方图维护的最佳实践
直方图和普通统计信息一样会过期。表数据发生大量插入、更新、删除后,原有直方图可能无法反映当前数据分布。建议在批量加载或维护窗口后重新执行RUNSTATS。对于高变更率的表,可以开启自动RUNSTATS功能,让DB2根据变更程度自动触发统计信息收集。自动收集作业同样可以包含WITH DISTRIBUTION选项,但需要关注倾斜列是否被覆盖。
不要为所有列都创建直方图。每个直方图都会占用目录表空间并延长RUNSTATS时间,只对频繁出现在查询条件中且数据倾斜严重的列进行收集。通常选择高基数列中的倾斜值、低基数列中的常见状态、日期和金额列等。可以通过分析应用查询模式确定候选列。创建后定期评估其效果,如果某列不再出现在关键查询中,可以删除不必要的直方图,重新执行不带WITH DISTRIBUTION的RUNSTATS即可清除旧直方图。
在表重组或大量数据变更后,建议先执行基础统计信息收集,再根据数据分布变化决定是否重新收集直方图。对于只读或低变更的表,可以拉长收集周期。还应该监控RUNSTATS作业的耗时,避免直方图收集占用过多维护窗口。对于超大表,可以使用采样以及多分区并行收集来提高效率。最后要注意不同DB2版本对直方图选项的支持存在差异,实际使用时应参考对应版本的官方文档。