传统查询执行引擎的局限性
说实话,我在刚入行做数据库内核开发那会儿,觉得传统查询执行引擎挺完美的。直到我在一个金融客户现场,亲眼看着一个白天跑得好好的查询,到了晚上批量作业时就挂了——执行计划没变,数据量变了,环境也变了。那一刻我才意识到,静态计划这东西,其实挺脆弱的。
这一章,我们就来聊聊传统查询执行引擎的三个硬伤。你想想看,一个查询计划在生成时就定死了所有操作顺序和算法,后面不管发生什么都不改——这在理想环境下没问题,但在真实生产环境中,问题就来了。
静态计划的问题
传统优化器生成的是静态执行计划。什么意思?就是优化器根据查询语句和当时的统计信息,一次性决定用哪个索引、哪种Join算法、哪个并行度。计划一旦生成,执行时就照单全收,绝不回头。
静态计划的三个致命缺陷:
- 计划与执行脱节:优化器做决策时,并不知道执行时数据到底长什么样
- 无法应对数据倾斜:Hash Join选好了,结果某个值占了90%的数据,直接炸内存
- 资源竞争无感知:计划生成时假设独占资源,实际可能跟其他查询抢CPU、抢IO
我记得有一次,一个简单的两表Join查询,优化器选了Nested Loop Join。测试环境跑得飞快,上了生产就慢得像蜗牛。为什么?因为测试表只有1万行,生产表有2000万行。优化器根据测试环境的统计信息做了决策,但执行时面对的是完全不同的数据量。这就是静态计划的典型翻车现场。
说白了,静态计划就像你出门前看天气预报决定穿什么衣服。如果天气突然变了,你只能硬扛着。数据库也一样——数据分布变了、资源竞争来了,计划不会自动调整。
统计信息误差的影响
优化器做决策的依据是什么?统计信息。直方图、唯一值数量、空值比例、数据分布……这些信息越准确,优化器选出来的计划就越靠谱。但问题是,统计信息永远是过去时。
| 统计信息类型 | 常见误差来源 | 对执行计划的影响 |
|---|---|---|
| 行数估算 | 数据频繁增删,统计信息未及时更新 | 选错Join算法(如Nested Loop vs Hash Join) |
| NDV(唯一值数量) | 数据倾斜导致估算偏差 | Hash Join的桶大小估算错误,内存溢出 |
| 直方图 | 采样率不足,边界值不准确 | 过滤条件选择性估算错误,索引选择失误 |
| 空值比例 | 空值分布变化未被捕获 | Join结果集估算偏差,影响排序和聚合策略 |
我在一个OLAP场景中遇到过这样的问题:一张事实表每天新增500万行,统计信息每周更新一次。优化器根据上周的统计信息,认为某个过滤条件能过滤掉99%的数据,选了索引扫描。但实际上,这周的数据分布变了,过滤条件只能过滤掉30%的数据。结果呢?索引扫描变成了灾难——每次回表都命中不同的数据页,IO爆炸了。
我的经验之谈:统计信息误差超过20%,优化器选错计划的概率就会急剧上升。很多DBA习惯设置统计信息自动更新的阈值,但说实话,在高吞吐场景下,这个阈值很难设对。设小了,更新太频繁,影响性能;设大了,误差累积,计划越来越差。
你可能会问:那多采样、多更新不就行了?嗯,这里有个矛盾——统计信息收集本身是有成本的。全量扫描一张10亿行的表来更新统计信息,可能比跑查询本身还慢。所以传统数据库只能在准确性和开销之间做权衡,而这个权衡,往往两头都不讨好。
运行时环境变化
就算统计信息是准的,执行计划也选对了,还有第三个坑——运行时环境的变化。这个因素,静态计划完全无法应对。
运行时环境变化主要包括三类:
- 系统负载波动:同一台机器上,其他查询突然来了,CPU、内存、IO资源被抢走
- 数据分布突变:比如批量导入后,某个字段的直方图完全变了样
- 硬件性能抖动:磁盘降速、网络延迟、NUMA节点内存访问不均衡
我曾经在一个生产环境中看到这样的场景:一个查询在凌晨3点跑只需要5秒,到了早上9点高峰期,同样的查询要跑3分钟。执行计划一模一样,但资源竞争完全不同。静态计划不会说“现在资源紧张,我换个省资源的算法吧”——它只会硬着头皮按原计划执行。
注意:运行时环境变化最可怕的地方在于——它不是偶发的,而是常态。生产环境的负载曲线就像心电图,永远在波动。静态计划假设环境是稳定的,这个假设本身就是错的。
还有一个容易被忽略的点:并发查询之间的相互影响。传统优化器在生成计划时,假设自己是唯一的查询。但实际上,多个查询同时运行时,它们会竞争锁、竞争内存、竞争IO带宽。静态计划不会考虑“如果其他查询占用了大量内存,我是不是该改用磁盘归并排序?”——它只会按原计划申请内存,然后发现不够了,开始往磁盘上刷数据,性能一落千丈。
下面这张图,我把传统查询执行引擎的三个局限性串起来,你看看它们之间的关系:
你看这张图,三个问题其实是相互关联的。统计信息误差导致优化器选错计划,运行时环境变化让原本合理的计划变得不合理,而静态计划的僵化又让系统无法自我修正。最终的结果就是——查询性能像过山车一样,今天5秒,明天5分钟,DBA天天被叫起来救火。
我个人觉得,传统查询执行引擎最大的问题不是技术上的,而是设计哲学上的——它假设世界是确定的、可预测的。但现实世界是不确定的、动态的。所以我们需要一种新的执行方式,能够在运行时感知环境变化,动态调整执行策略。这就是自适应查询执行要解决的问题。
嗯,这一章的内容就到这里。你理解了传统引擎的局限性,后面再看自适应查询执行的设计思路,就会觉得——哦,原来这些痛点,人家早就考虑到了。