DB2的物化查询表(MQT)本质上是一张预先计算并存储查询结果的物理表,它把原本需要实时聚合、连接、过滤的重负载操作提前完成,查询时优化器可以直接读取这张“结果缓存”,省去大量CPU和I/O消耗。创建MQT的语法并不复杂,但很多项目组在投入使用后发现执行计划并没有如预期切换到MQT上,查询性能甚至和没建表时一模一样。排查半天才意识到,问题不在MQT的定义是否正确,而在于统计信息这一环断了。DB2优化器是基于成本(Cost-Based Optimizer)做决策的,如果MQT没有及时收集统计信息,优化器就无法准确估算它的行数、块数、分布情况,也就无法判断走MQT是否真的比走基表更便宜。

统计信息的重要性在数据仓库场景中尤其突出。数据仓库的表动辄上亿行,聚合查询的中间结果量可能只有几万行甚至几千行,如果优化器认为MQT和基表成本接近,往往会保守地选择已经建立完整索引和统计的基表,而不是冒险去读取一个统计信息陈旧的MQT。此时需要一种机制让DB2在MQT创建或刷新后自动完成RUNSTATS,并保持统计信息与数据变化同步。opt_mqt_stats数据库配置参数就是为这个目的设计的。它控制着DB2是否在MQT刷新时自动收集分布统计信息,从而让优化器始终拥有准确的成本输入。
opt_mqt_stats参数的作用与底层行为
opt_mqt_stats是DB2数据库级配置参数,取值只有ON和OFF两种。默认情况下这个参数是OFF,也就是说即使你创建了MQT,DB2也不会主动为它做统计收集,除非手工执行RUNSTATS命令。当设置为ON后,任何导致MQT数据发生变化的操作——比如REFRESH TABLE、增量刷新、或者由系统自动维护的立即刷新——都会触发DB2在后台对MQT执行统计收集。这个统计收集不是全表扫描所有数据的完整RUNSTATS,而是基于变更数据量的增量式统计更新,旨在以较小的性能代价换取统计新鲜度。
从实现机制上看,opt_mqt_stats=ON时,DB2会在MQT刷新操作结束前调用内部统计例程,收集该MQT的行数、索引键基数、列分布直方图等必要信息。这些信息写入系统目录表(例如SYSSTAT.TABLES、SYSSTAT.COLUMNS)之后,后续的查询编译就能读到准确的MQT成本数据。如果MQT特别大,一次刷新可能涉及几百万行的新增或删除,那么自动统计收集也会消耗一定的时间和临时空间。但相比因为没有统计信息而导致优化器永远不走MQT的损失,这点开销通常可以接受,尤其是对于静态数据仓库或夜间批量刷新场景。
需要注意的是,opt_mqt_stats只作用于已经定义为MQT的对象,普通表、昵称、临时表不受该参数影响。如果一个数据库中有多个MQT,开启该参数后所有MQT的刷新都会触发统计收集。如果只想对个别MQT做自动统计,可以针对性地在MQT上创建触发器或调度RUNSTATS作业,而不是全局开启参数。此外,该参数需要DBA权限修改,修改后立即生效,不需要重启实例。可以通过db2 update db cfg for sample using opt_mqt_stats on来开启,或者使用db2 get db cfg for sample show detail查看当前值。
开启前的准备与参数配置实例
在修改opt_mqt_stats之前,建议先检查当前数据库中MQT的统计信息状态。执行下面的SQL可以查看所有MQT的最后统计时间:
SELECT TABNAME, STATS_TIME, CARD, NPAGES FROM SYSCAT.TABLES WHERE TYPE = 'M' ORDER BY TABNAME;
如果STATS_TIME为NULL或者远早于最近一次REFRESH时间,说明MQT统计已经过期,优化器无法依据这些数据做出合理判断。此时可以先手工对MQT执行一次完整RUNSTATS,让优化器有一个正确的初始估计:
RUNSTATS ON TABLE myschema.sales_summary_mqt WITH DISTRIBUTION AND DETAILED INDEXES ALL;
完整RUNSTATS能够收集列数据分布、高频值、分位数等详细信息,对于复杂谓词的基数估计非常有帮助。执行完成后再次查询SYSCAT.TABLES确认STATS_TIME已经更新。紧接着再开启自动统计参数:
UPDATE DB CFG FOR sample USING opt_mqt_stats ON;
确认生效:
GET DB CFG FOR sample SHOW DETAIL;
参数值显示为ON后,所有后续MQT刷新都会自动更新统计。对于已经生产运行中的数据库,建议在低峰期执行完整RUNSTATS,避免对业务查询造成影响。如果MQT体积很大,完整RUNSTATS可能需要数分钟甚至更久,务必评估好时间窗口。
验证opt_mqt_stats带来的执行计划变化
开启参数前后的执行计划对比是验证效果最直接的方式。以下面这个典型的销售汇总查询为例,假设存在一个名为sales_summary_mqt的物化查询表,它预先按地区和月份聚合了销售明细表sales_fact的数据:
SELECT region, SUM(amount) FROM sales_fact WHERE sale_date BETWEEN '2024-01-01' AND '2024-03-31' GROUP BY region;
开启opt_mqt_stats之前,即使优化器理论上可以通过路由到sales_summary_mqt来替代对sales_fact的全表扫描,但由于MQT统计陈旧,优化器可能高估了MQT扫描成本,最终执行计划显示为TBSCAN(表扫描)在sales_fact上聚合。开启参数并完成一次自动统计收集后,优化器就能正确估算MQT的行数远小于基表,执行计划会变为MATERIALIZED QUERY TABLE ACCESS,IO和CPU消耗大幅下降。
可以用下面的命令获取实际执行计划:
db2 set current explain mode explain db2 "SELECT region, SUM(amount) FROM sales_fact WHERE sale_date BETWEEN '2024-01-01' AND '2024-03-31' GROUP BY region" db2 set current explain mode no db2exfmt -d sample -g TIC -w -1 -n % -s % -# 0 -o explain.out
在explain.out文件中搜索MQT或MATERIALIZED关键字,如果出现了MQT访问节点,说明优化器已经选择该路径。同时对比开启前后的ESTIMATED_COST和BUFFERS值,可以看到成本显著下降。有些情况下优化器仍然选择基表,可能是因为查询中包含了非确定函数、用户定义函数或优化器无法匹配的谓词,这时需要检查MQT定义是否覆盖了查询的所有列和过滤条件。
维护MQT统计的常见误区与性能权衡
第一个常见误区是认为只要开启了opt_mqt_stats就可以完全放弃手工RUNSTATS。这个参数执行的是基于刷新操作的增量统计更新,对于列数据分布发生剧烈变化(例如新增了大量偏斜值)的MQT,增量更新可能不足以反映新的分布特征。因此最佳实践是“自动统计为主,定期完整RUNSTATS为辅”。比如每周或每月在维护窗口执行一次完整RUNSTATS,平时依赖自动参数保持统计新鲜度。
第二个误区是忽视MQT刷新频率与统计开销的平衡。如果MQT被设置为REFRESH IMMEDIATE且基表频繁更新,那么每次基表DML都会触发MQT同步维护,开启opt_mqt_stats后还会叠加统计收集开销,可能拖慢写入性能。这种情况下可以评估将MQT改为REFRESH DEFERRED,在业务低峰期批量刷新,或者保持opt_mqt_stats=OFF,通过调度器在刷新作业后追加RUNSTATS步骤。
第三个误区是忽略MQT上的索引统计。MQT本身为了提高查询效率常常会创建索引,如果只收集了表统计而没有收集索引统计,优化器在评估索引扫描路径时仍然会缺乏数据。虽然opt_mqt_stats在自动统计时默认会包含索引统计,但在手工RUNSTATS时需要显式指定AND DETAILED INDEXES ALL。建议定期检查SYSCAT.INDEXES的STATS_TIME,确保索引统计不过期。
综合来看,opt_mqt_stats是DB2中一个低门槛、高收益的优化开关。对于数据仓库、BI报表、聚合分析类应用,开启该参数能够显著提高优化器选择MQT的概率,避免“建了MQT却用不上”的尴尬。对于OLTP环境或MQT刷新极其频繁的场景,需要结合写入性能测试结果谨慎启用,或者采用定制化的统计收集策略。在生产环境变更前,务必在测试库中模拟真实负载,对比开启前后的查询响应时间和写入吞吐量,用数据指导决策。
DB2opt_mqt_statsMQT统计修改时间:2026-08-30 08:53:00