DB2的优化器分为基于规则的RBO和基于成本的CBO两种模式,现代DB2(LUW版本)默认使用基于成本的优化器CBO。CBO的核心思想是:根据表的统计信息、索引信息、系统资源参数(如CPU速度、并行度、通信开销)等信息,为每一条SQL估算出多个候选执行计划的成本,然后选择成本最低的那个计划执行。换句话说,统计信息越准确、参数配置越合理,优化器选出的执行计划就越靠谱。很多性能问题最终都能追溯到统计信息陈旧或优化级别设置不当这两个原因上。

一、理解CBO的工作原理与成本模型
CBO在估算成本时,主要依赖几个输入:表的基数(行数)、列的分布统计(频率、分位数)、索引的聚集度(CLUSTER FACTOR)、页数量、CPU速度以及I/O开销模型。优化器把这些因素代入成本公式,得出一个加权后的总成本值。这里面任何一个输入失真,都会导致估算成本偏离真实执行成本。
举个典型的例子:一张订单表有1亿行,其中status列有严重的数据倾斜,90%的值是COMPLETED。如果只收集了基础统计而没有收集列的分布统计,优化器会认为status='COMPLETED'只会命中10%的行,从而选择索引扫描。而实际上这条查询会命中9000万行,走索引反而比全表扫描慢得多。这就是统计信息不准确导致的错误计划。
另外要明白一点:CBO估算的是逻辑成本,不是精确的执行时间。它假设内存充足、I/O均匀,所以当实际环境中存在严重的锁等待、缓冲池命中率波动时,估算成本最低的计划未必是实际执行最快的计划。这也是为什么调优时不能只看成本数字,还要结合实际执行时间和监控数据一起判断。
二、核心参数详解:OPTLEVEL与DFT_QUERYOPT
DB2通过数据库配置参数DFT_QUERYOPT设定默认的查询优化级别,取值范围是0到13(不同版本略有差异),最常用的是3和5。级别0使用最少的优化手段,编译速度快但计划质量一般;级别5会尝试更复杂的连接顺序枚举和查询改写,编译耗时更长,计划质量通常更好;级别7及以上会启用更激进的动态编程策略,适合复杂报表查询,但对简单OLTP语句来说是浪费编译时间。
修改数据库默认优化级别的命令如下:
-- 查看当前默认优化级别 DB2 GET DB CFG FOR SAMPLE | FINDSTR DFT_QUERYOPT -- 修改默认优化级别为5 DB2 UPDATE DB CFG FOR SAMPLE USING DFT_QUERYOPT 5 -- 针对单个会话设置优化级别(不需要重启) SET CURRENT QUERY OPTIMIZATION = 3;
这里有一个实用的经验法则:OLTP系统中,语句简单、执行频繁、要求编译时间短,用级别1或3就够了;数据仓库或者复杂的分析查询,建议级别5或7。如果发现某条复杂SQL的执行计划明显不优,可以尝试在会话级别临时提高优化级别对比效果,确认有效后再固化到应用或注册变量中,而不是直接改数据库级默认值影响所有语句。
除了DFT_QUERYOPT,注册变量DB2_REDUCED_OPTIMIZATION也值得关注。它可以在保持较高级别的同时,限制优化器在某些规则上花费的时间,实现编译时间与计划质量的折中。设置方法是db2set DB2_REDUCED_OPTIMIZATION=5,修改后需要重启实例生效。
三、统计信息:runstats是CBO的生命线
前面反复强调统计信息的重要性,收集统计信息靠的是RUNSTATS命令。最基础的写法是收集表和索引的基础统计:
-- 收集表和全部索引的基础统计信息 RUNSTATS ON TABLE DB2INST1.ORDERS WITH DISTRIBUTION AND DETAILED INDEXES ALL; -- 使用并行度加快收集速度(大表推荐) RUNSTATS ON TABLE DB2INST1.ORDERS WITH DISTRIBUTION AND DETAILED INDEXES ALL NUM_FREQUENCY_VALUES 100 NUM_QUANTILES 100 SAMPLE DETAILED;
WITH DISTRIBUTION会收集列值的频率分布和分位数信息,这对存在数据倾斜的列非常关键。NUM_FREQUENCY_VALUES指定收集最频繁值的个数,NUM_QUANTILES指定分位数数量,这两个值越大,优化器对过滤条件的估算越精确,但收集时间也越长。一般对数据倾斜明显的列设置100左右就够用了。
统计信息会随数据变化而失效。经验做法是:当表中数据变化超过10%到20%时重新收集。DB2 9.7之后提供了自动统计信息收集(AUTO_MAINT相关配置),建议在生产环境开启,让系统在维护窗口自动评估并收集过期统计。可以通过syscat.tables中的STAT_TIME字段查看上次收集时间,判断统计是否陈旧。
还有一点容易被忽略:收集完统计后,如果使用了静态SQL(包绑定方式),需要执行REBIND让新统计生效;动态SQL的语句缓存也可能持有旧计划,必要时可以flush包缓存,避免改了统计却看不到计划变化的情况。
四、验证与诊断:让执行计划说话
调参之后必须验证效果,DB2常用的工具是EXPLAIN和db2exfmt。先创建解释表(一般位于sqllib/misc目录下的EXPLAIN.DDL脚本),然后在会话中设置EXPLAIN模式执行SQL,再用db2exfmt格式化输出执行计划:
-- 开启当前会话的解释模式 SET CURRENT EXPLAIN MODE EXPLAIN; -- 执行目标SQL(不会真正执行,只生成计划) SELECT o.order_id, c.customer_name FROM orders o JOIN customer c ON o.cust_id = c.cust_id WHERE o.order_date > '2024-01-01'; -- 关闭解释模式 SET CURRENT EXPLAIN MODE NO; -- 在命令行导出格式化的执行计划 -- db2exfmt -d SAMPLE -1 -o plan.txt
看执行计划时重点关注几个指标:各算子的估算行数(CARD)、总成本(TOTAL COST)、是否选用了预期索引、连接方式是NLJOIN还是HSJOIN、排序是否落在SORT堆中溢出。如果估算行数和实际行数差距悬殊(比如估算100行实际100万行),基本可以断定是统计信息问题;如果统计没问题但连接方式不理想,就要考虑优化级别、索引设计或者通过优化概要(OPTIMIZATION PROFILE)强制指定连接顺序。
优化概要是DB2提供的计划控制手段,当优化器反复选错计划时,可以在XML概要文件中指定使用某个索引或某种连接方式,并通过SET CURRENT OPTIMIZATION PROFILE启用。这是最后手段,不建议滥用,因为数据分布变化后强制的计划可能反而变差。总结一下调优的优先级:先保证统计信息准确,其次调整优化级别和系统类参数,最后才考虑用概要硬性干预计划。按照这个顺序排查,绝大多数CBO相关的性能问题都能定位并解决。