执行计划选错索引或连接顺序,很多时候根源不在SQL写法,而在优化器对过滤条件返回行数的估算。DB2优化器计算谓词选择性时,默认把不同列上的过滤条件视作互相独立事件,用各列单独选择度相乘得到组合选择度。这个假设在列与列之间存在业务关联时会严重失真,例如省份字段与城市字段同时出现在where条件里,真实数据里城市几乎只属于某一个省,但独立相乘会得到一个远小于实际行数的估算值。估算偏低会让优化器误判索引回表成本低,选择不合适的嵌套循环连接,或者高估筛选收益而放弃半连接转换。

opt_pred_stats就是针对这一问题设计的谓词统计优化能力。它并不修改数据本身,而是改变优化器读取和利用统计信息的方式:允许优化器在谓词相关性判断中使用列组统计、频率分布和更细粒度的分位数数据。通过列组统计,DB2可以知道城市和省份组合的真实基数,而不是简单地把两个单列基数相乘。换句话说,opt_pred_stats让优化器从基于独立假设的选择度估算,切换到基于真实数据分布的组合基数估算。这个能力在多列过滤、范围条件与等值条件混合、以及外连接谓词下推场景中尤其重要。
谓词选择性失真与opt_pred_stats的定位
要理解opt_pred_stats为什么有价值,需要先看DB2优化器在没有列组统计时如何处理组合谓词。假设orders表有100万行,city列有500个不同值,province列有30个不同值。如果用户执行查询SELECT COUNT(*) FROM orders WHERE city='Hangzhou' AND province='Zhejiang',优化器会先读取单列统计:city的过滤因子约为1/500,province的过滤因子约为1/30。按照独立事件假设,组合过滤因子为1/500乘以1/30,也就是1/15000,估算返回行数大约67行。但实际数据中Hangzhou只位于Zhejiang,真实组合基数接近20万行。这个量级的误差会让优化器错误判断回表代价。
opt_pred_stats的核心作用是在优化器生成访问方案之前,检查系统目录中是否存在可用于当前谓词组合的列组统计数据。如果存在,优化器会直接使用该列组的频率或分位数信息计算组合过滤因子,不再依赖单列相乘。这样处理之后,基数估算从67行修正到接近20万行,执行计划就可能从全表扫描切换为合适的索引范围扫描,或者调整连接顺序,避免将大结果集作为内表反复探测。值得注意的是,opt_pred_stats并不是孤立开关,它需要与RUNSTATS收集到的列组统计、频率分布和直方图配合才有效果。没有统计基础,优化器即使想用相关列信息也无从读起。
从技术实现角度看,DB2优化器在谓词重写和基数估算阶段会检查每个谓词关联的列集合。如果发现多个过滤条件作用在同一组列上,并且该列组在SYSSTAT.COLGROUPS或对应系统目录中存在统计记录,opt_pred_stats就会触发组合选择度计算。反之,如果只有单列统计,优化器只能沿用独立假设。这也是为什么很多DBA发现开启了某个优化参数却看不到执行计划变化——根本原因是列组统计没有被收集,参数失去了数据支撑。
列组统计的收集与opt_pred_stats配置
要让opt_pred_stats真正发挥作用,第一步是收集列组统计。DB2默认的RUNSTATS命令只收集单列统计,除非显式指定COLGROUPS子句。列组的选择不是越多越好,应当根据慢SQL中反复出现的多列过滤条件来确定。例如订单表经常按city和province同时过滤,或者按category与brand组合筛选,就应该把这些列组纳入统计范围。下面这组命令展示了如何为orders表收集带分布统计的列组信息。
RUNSTATS ON TABLE db2inst1.orders WITH DISTRIBUTION AND DETAILED INDEXES ALL COLGROUPS ((city, province), (category, brand))
WITH DISTRIBUTION会生成频率分布和分位数统计,对于数据倾斜较大的列尤其重要。COLGROUPS里每一对括号代表一个列组,DB2会记录这些列组合的不同值数量、最频繁值以及组合频率。收集完成后,可以通过系统目录视图检查列组统计是否存在,使用类似下面的查询确认统计收集成功。
SELECT colgroupname, colcard, numfreqvalues FROM syscat.colgroups WHERE tabname = 'ORDERS'
有了列组统计之后,再启用opt_pred_stats才能看到效果。在DB2优化概要或数据库配置中,可以指定谓词统计的使用策略。不同版本的DB2对opt_pred_stats的暴露方式有所不同,部分版本通过优化概要文件中的统计指令控制,部分版本允许通过注册变量或数据库配置参数开启。无论哪种方式,核心思想都是告诉优化器:在多列过滤场景下优先读取列组统计,而不是简单套用独立选择度乘积。配置完成后需要重新绑定相关包,或让动态SQL重新进入编译流程,否则已经缓存的访问方案不会自动更新。
一个常见的落地步骤是:先使用db2set或优化概要配置启用谓词统计优化,再对目标表执行RUNSTATS收集列组分布,最后通过FLUSH PACKAGE CACHE或重新绑定包强制优化器生成新方案。这样做能够避免因为缓存旧计划而掩盖统计优化的效果。例如可以执行下面的命令刷新动态SQL缓存。
FLUSH PACKAGE CACHE DYNAMIC
从执行计划验证优化效果
统计信息和参数调整完成后,不能只看优化器估算的数字,还要回到执行计划中验证。DB2的db2exfmt工具可以导出详细的访问方案,包括每个操作符的基数估算、过滤因子、表访问方式和连接顺序。优化前可以先记录一下执行计划中过滤后的估算行数,然后重新收集列组统计并启用opt_pred_stats,再导出一次执行计划,对比同一查询的基数估算变化。
例如一个典型的多列过滤查询,优化前计划中表扫描操作符的估算返回行数可能只有几百行,但实际执行后通过快照发现返回了几万行。这种低估会让优化器选择索引扫描加嵌套循环,但实际运行时内表被反复读取几万次,性能急剧下降。启用opt_pred_stats并收集列组统计后,优化器将估算行数修正到接近真实值,计划可能切换为哈希连接或全表扫描加排序合并连接。下面是一组导出执行计划的命令示例。
db2 connect to sample db2 set current explain mode explain db2 "SELECT COUNT(*) FROM orders WHERE city = 'Hangzhou' AND province = 'Zhejiang'" db2 set current explain mode no db2exfmt -d sample -g TIC -w -1 -n % -s % -# 0 -o plan_explain.txt
在导出的计划文件中,重点查看操作符下方的Rows预估值和Filter Factor。如果优化器能够识别列组统计,组合过滤因子通常会明显高于独立相乘的结果。比如独立相乘给出的过滤因子可能是0.000067,而列组统计给出的过滤因子接近0.2,基数估算随之增加。这种变化能够帮助优化器避免选择错误的嵌套循环,或者调整连接表的内外顺序。验证时还应当结合实际执行时间、缓冲池命中率和锁等待情况,综合判断执行计划是否真正改善。
使用边界与统计维护成本
opt_pred_stats并不是万能药,列组统计的收集和维护都有成本。每增加一个列组,RUNSTATS就需要扫描更多组合值分布,消耗更多临时空间和时间。对于数据量达到数亿行的大表,列组统计的收集可能比单列统计多出数倍耗时。因此列组选择应当克制,只针对慢SQL中确实存在强相关性的过滤条件,而不是把所有可能组合都加进去。可以先用执行计划分析定位基数估算偏差最大的谓词组合,再有针对性地收集。
数据频繁变更的情况下,列组统计也会快速过期。如果表每天有大量插入、更新和删除,而统计信息只每周收集一次,优化器后期拿到的组合基数可能与实际相差很远,opt_pred_stats反而会放大错误。此时需要调整统计收集策略,可以配合DB2的自动统计收集任务设置合适的维护窗口,或者对变更频繁的表提高采样率和收集频率。对于数据倾斜特别严重的列组,还可以使用NUM_FREQVALUES和NUM_QUANTILES参数增加统计细节,但代价是系统目录表占用更多空间。
还有一点需要明确,opt_pred_stats改善的是优化器的基数估算能力,它不会改变SQL语义,也不能修复因为索引缺失或统计完全缺失导致的问题。如果查询性能瓶颈来自缺少合适的索引、锁等待或者排序溢出,单纯调整谓词统计并不会有明显效果。正确的做法是把opt_pred_stats作为执行计划调优工具箱中的一个环节,和索引设计、统计收集、参数调整、SQL重写配合使用。先从执行计划中找到基数估算严重偏离的节点,再针对该节点涉及的列组收集统计并启用谓词统计优化,最后通过实际运行确认改善,这样形成一个闭环调优过程。
DB2 opt_pred_stats谓词统计SQL优化修改时间:2026-09-22 22:54:20