数据库优化器的职责通常被概括为“寻找成本最低的执行计划”,但很多人忽略了在这之前还有一个关键阶段——查询重写。DB2的SQL编译器在接收到一条SQL后,会先对其进行语法和语义检查,随后进入查询图模型构建与重写环节。查询重写引擎持有一系列规则,每条规则都会尝试匹配查询图中的特定模式,一旦匹配成功,就将其转换为逻辑上等价但执行效率更高的形式。这个阶段的输出再交给基于成本的优化器进行物理计划选择,因此重写的好坏直接影响了成本估算的起点。

与很多人的直觉相反,查询重写并不是对原始SQL的文本级替换,而是在内部查询图上的结构化变换。这使得即便是书写风格迥异的SQL,只要逻辑一致,最终很可能被归一化为相同的内部表示。例如,WHERE col IN (SELECT …)与WHERE EXISTS (SELECT …)在重写后可能会形成相同的半连接结构。理解这一点,我们就能跳出对语法糖的纠结,转而关注影响重写决策的深层因素。
谓词下推与过滤因子
谓词下推是查询重写中最基础也最有效的规则之一。它的核心思想是让过滤条件尽可能早地在查询计划中执行,从而减少后续操作处理的行数。在DB2中,这一规则不仅作用于普通的WHERE子句,还会将与分组列相关的HAVING条件尝试下推到WHERE阶段,甚至将外连接中隐含的过滤条件向表扫描方向推入。例如,对于SELECT * FROM A LEFT JOIN B ON A.id = B.id WHERE B.col = 'value',重写引擎会发现此条件会将外连接退化为内连接,于是将连接类型直接改写,并将B.col = 'value'下推到表B上提前过滤。
谓词下推能否发生,不仅取决于条件本身,还与连接语义、NULL值传递规则密切相关。当查询包含视图时,重写引擎会首先将视图展开为基表,但有时视图定义中包含了聚合或DISTINCT操作,此时直接下推谓词可能会改变语义,因此DB2会采用更为保守的“谓词提升”策略,先将视图当作黑盒处理,评估是否可以通过等价变换将部分条件应用到视图内部。为了让谓词下推更有效地工作,我们应确保表上的列统计信息足够准确,因为优化器需要借助过滤因子估计下推后减少的行数,如果统计信息缺失,就可能放弃本可进行的高效重写。
另一个容易被忽略的细节是,谓词下推与索引访问之间存在耦合。当过滤条件被下推到表扫描后,DB2的优化器便可以在该表上考虑索引匹配。如果原始查询中条件被包裹在多层视图或派生表中,没有重写的帮助,优化器只能对最外层的条件进行索引选择,内层的大表扫描将无法利用索引,导致性能大幅下降。
子查询转换与连接优化
子查询是SQL中非常强大的表达能力,但同时也是性能陷阱的高发区。DB2的查询重写引擎包含了丰富的子查询处理规则,目标是尽可能将子查询转换为连接或半连接,从而打破嵌套执行的开销。对于IN子查询,重写器会分析内外层表的关系,如果可以保证唯一性或者有DISTINCT消除重复,就会生成标准的内连接;如果外层表需要检查“是否存在”,则转换为EXISTS形式的半连接,由哈希半连接或索引查找完成。对于NOT IN和NOT EXISTS,则对应反半连接或反连接。
特别值得注意的是相关子查询的处理。当子查询引用了外层列时,默认的执行方式是为外层每一行评估一次子查询,这在数据量大时几乎不可接受。DB2的重写器会将相关子查询去相关化,通过引入额外的连接条件,将子查询从循环依赖中解放出来。例如,经典的SELECT * FROM emp e WHERE salary > (SELECT AVG(salary) FROM emp WHERE dept = e.dept)可以被重写为先按部门计算平均工资,再与员工表连接。如果这个变换失败,往往是因为子查询内部存在聚合、窗口函数或不确定性表达式,导致重写器无法保证等价性。开发者在编写此类SQL时,可以通过将子查询改写为CTE或直接使用窗口函数,降低重写的复杂度,给优化器更多选择。
此外,DB2还支持一种强大的连接删除规则:当查询只从一个表中取列,并且该表与另一个表之间存在参照完整性约束,且连接条件保证了不会丢失或增加任何行时,优化器可以直接删除那个对最终结果集不产生影响的表连接。这种重写依赖于正确的主键-外键关系定义,因此为表添加相关的约束信息不仅仅是数据完整性的需要,更是一种性能优化的手段。
物化查询表(MQT)的自动匹配
物化查询表是DB2中一项极富特色的重写机制。它允许用户预先定义并填充一个包含聚合或连接结果的物理表,当传入查询与MQT的定义在逻辑上相符时,查询重写引擎会自动将原查询重写为直接读取MQT,从而省去昂贵的运行时计算。这种匹配不是简单的文本比对,而是基于查询图的重写关系。例如,一个按月汇总销售额的MQT,不仅能够服务于完全相同的查询,还可以被一个按季度汇总的查询所使用——只要重写器能证明从月汇总推导出季度汇总是正确的。
要让MQT自动匹配生效,需要满足几个关键条件:MQT必须处于REFRESH DEFERRED状态且通过完整性检查,或者被显式设置为MAINTAINED BY SYSTEM;查询的隔离级别需要允许读取未提交或版本数据,因为MQT可能与基表存在微小的数据滞后;此外,必须在连接或会话级别将专用寄存器CURRENT REFRESH AGE设置为ANY,以告知优化器可以使用非实时数据。很多开发者发现MQT没有生效,往往是忽略了最后这个设置,导致优化器因保守策略而拒绝重写。
在重写过程中,MQT匹配规则还会尝试进行补偿计算。例如原查询需要的是过去7天的数据,而MQT是按天预聚合的,重写器会扫描MQT中最近7天的记录并求和,而不是回退到基表。这种能力使得单个MQT能够覆盖比其定义更宽的查询范围,极大地提高了其投资回报率。因此,在设计MQT时,应尽量以细粒度的聚合或连接为基础,让重写器有更大的组合空间,而不是为每一个具体需求创建一张专用表。
诊断重写过程的实践方法
即使了解了规则,实际环境中查询究竟被重写成了什么样子,依然需要工具来验证。DB2提供了db2exfmt工具,通过解释表捕获优化器在整个编译过程中产生的详细信息,其中包括“优化的语句”部分——这就是查询重写的最终产出。开发者可以对比原始SQL和重写后的语句,直观地看到哪些视图被合并了、子查询是否被转换、MQT是否被使用等。在重写后的语句中,往往会出现像SYSIBM.SYSDUMMY1的访问、新的内部别名或者消失的表引用,这些都是重写留下的痕迹。
此外,在开发环境中,还可以通过设置DB2_OPTIMIZATION_LEVEL环境变量或使用优化概要,临时禁用某些重写规则,对比前后的执行计划和开销,从而准确定位究竟是哪条规则没有达到预期效果。但要注意,在生产系统中此类调整需格外谨慎,因为重写规则的连锁反应可能导致整体计划劣化。更稳妥的做法是调整SQL写法、表结构或统计信息,为正面的重写创造条件,而不是关闭规则本身。
查询重写不是一套静止的开关,它是数据库内核中持续演进的核心组件。新版本的DB2往往会引入更多重写规则,例如针对窗口函数的去重计算、针对递归查询的剪枝变换等。养成阅读优化报告、关注编译器升级变化的习惯,才能在复杂SQL的优化中做到游刃有余,让数据库的智能特性真正为己所用。
DB2查询重写Query_Rewrite修改时间:2026-08-12 20:07:14