一、自适应查询优化概述
1.1 什么是自适应查询优化
自适应查询优化,说白了就是让数据库在执行SQL的过程中,能根据实际运行情况动态调整执行计划。
我刚开始接触这个概念时,也觉得有点玄乎。传统的做法是:SQL进来,优化器生成一个计划,然后一条路走到黑。但自适应不一样——它会边执行边观察,发现不对劲就立刻调整。
举个例子。你写了个多表关联查询,优化器估算A表返回100行,B表返回50万行。它选择先查A表,再用嵌套循环去连B表。结果实际一跑,A表返回了10万行。这时候自适应优化就会说:「等等,情况不对,我得换个策略。」于是它把执行计划从嵌套循环换成了哈希连接。
整个过程对用户完全透明。你不需要改SQL,不需要加hint,数据库自己就搞定了。
1.2 为什么需要自适应
这个问题我经常被问到。很多开发同学觉得:「优化器不是已经很智能了吗?为什么还要搞自适应?」
嗯,这里有个关键点——优化器再聪明,它也是基于估算做决策的。而估算,天然就有误差。
我在项目中遇到过这样一个场景:一张订单表,平时数据量200万,优化器统计信息很准确。但双十一那天,数据量暴涨到5000万。优化器还是按200万来算,生成的计划完全跑不动。那次事故让我印象特别深——如果当时有自适应优化,系统自己就能感知到数据量变化,及时切换执行策略。
自适应优化的必要性,主要体现在三个方面:
- 统计信息过时——数据分布变了,但统计信息没来得及更新
- 参数嗅探问题——同一个SQL,不同参数值导致的行数差异巨大
- 复杂查询的不确定性——多表关联、子查询、CTE,中间结果很难准确预估
1.3 与传统查询优化的区别
传统查询优化和自适应查询优化,本质区别在于「什么时候做决策」。
传统优化是编译时决策——SQL解析完,优化器生成计划,然后执行器老老实实按计划跑。中间不管实际情况怎么变,计划都不改。
自适应优化是运行时决策——执行过程中持续收集反馈,发现偏差就调整。调整的粒度可以很细,比如只调整某个join操作,而不是推翻整个计划。
我习惯用一张表来对比它们的差异:
| 对比维度 | 传统查询优化 | 自适应查询优化 |
|---|---|---|
| 决策时机 | 编译阶段(一次性) | 编译+运行阶段(持续) |
| 依赖信息 | 统计信息、系统配置 | 统计信息 + 运行时反馈 |
| 应对数据变化 | 被动(需手动更新统计信息) | 主动(自动感知并调整) |
| 参数敏感度 | 高(参数嗅探问题突出) | 低(可根据实际参数调整) |
| 执行计划稳定性 | 固定不变 | 动态变化 |
| 适用场景 | 简单查询、数据分布稳定 | 复杂查询、数据倾斜、参数多变 |
你想想看,传统优化就像「按图索骥」,地图画好了就照着走。自适应优化则是「边走边看」,发现路不通就换条道。哪种更靠谱?答案不言自明。
不过我也得提醒一句:自适应不是万能的。它本身有开销——监控、反馈、调整都需要消耗资源。对于简单查询,这点开销可能得不偿失。所以数据库通常只对复杂查询启用自适应优化。
1.4 自适应优化的核心流程
自适应优化到底是怎么工作的?我画了一张流程图,把它的核心环节串起来:
这个流程里,最关键的环节就是「偏差判断」。数据库会设定一个阈值,比如实际行数是估算行数的2倍以上,就触发调整。调整的粒度可以是整个计划,也可以是某个算子——比如只把嵌套循环换成哈希连接,其他部分保持不变。
我个人习惯把自适应优化看作一个「反馈闭环」:执行→监控→判断→调整→再执行。这个闭环跑得越快,查询性能就越稳定。
1.5 自适应优化的典型实现
目前主流数据库对自适应优化的支持程度不同。我整理了几个常见产品的实现方式:
| 数据库 | 自适应特性 | 适用版本 |
|---|---|---|
| SQL Server | 自适应Join(运行时切换嵌套循环/哈希/合并连接) | 2017+ |
| Oracle | 自适应执行计划、动态统计信息 | 12c+ |
| MySQL 8.0 | 自适应哈希索引、Join Buffer自适应 | 8.0+ |
| PostgreSQL | 自适应扫描、并行度自适应(通过扩展) | 12+(部分特性) |
以SQL Server为例,它的自适应Join功能是我用得最多的。它会在执行前生成多个候选计划,执行时根据实际行数选择最优的那个。切换过程对查询完全透明,你甚至不需要重建统计信息。
Oracle的自适应执行计划更激进一些——它可以在执行过程中动态改变计划。比如一开始用嵌套循环,发现数据量大了,直接换成哈希连接。这种「热切换」能力,在应对数据倾斜时特别有用。