传统查询执行引擎的局限性

说实话,我在刚入行做数据库内核开发那会儿,觉得传统查询执行引擎挺完美的。直到我在一个金融客户现场,亲眼看着一个白天跑得好好的查询,到了晚上批量作业时就挂了——执行计划没变,数据量变了,环境也变了。那一刻我才意识到,静态计划这东西,其实挺脆弱的。

这一章,我们就来聊聊传统查询执行引擎的三个硬伤。你想想看,一个查询计划在生成时就定死了所有操作顺序和算法,后面不管发生什么都不改——这在理想环境下没问题,但在真实生产环境中,问题就来了。

静态计划的问题

传统优化器生成的是静态执行计划。什么意思?就是优化器根据查询语句和当时的统计信息,一次性决定用哪个索引、哪种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带宽。静态计划不会考虑“如果其他查询占用了大量内存,我是不是该改用磁盘归并排序?”——它只会按原计划申请内存,然后发现不够了,开始往磁盘上刷数据,性能一落千丈。

下面这张图,我把传统查询执行引擎的三个局限性串起来,你看看它们之间的关系:

传统查询执行引擎的三大局限性 传统查询执行引擎的三大局限性 静态执行计划 统计信息误差 运行时环境变化 静态计划僵化 行数估算偏差 NDV估算不准 系统负载波动 数据分布突变 算法无法切换 并行度固定 查询性能不可预测,运维成本高 核心矛盾 优化器在计划生成时做决策,但执行时的数据分布、系统负载、资源竞争都是未知的

你看这张图,三个问题其实是相互关联的。统计信息误差导致优化器选错计划,运行时环境变化让原本合理的计划变得不合理,而静态计划的僵化又让系统无法自我修正。最终的结果就是——查询性能像过山车一样,今天5秒,明天5分钟,DBA天天被叫起来救火。

我个人觉得,传统查询执行引擎最大的问题不是技术上的,而是设计哲学上的——它假设世界是确定的、可预测的。但现实世界是不确定的、动态的。所以我们需要一种新的执行方式,能够在运行时感知环境变化,动态调整执行策略。这就是自适应查询执行要解决的问题。

嗯,这一章的内容就到这里。你理解了传统引擎的局限性,后面再看自适应查询执行的设计思路,就会觉得——哦,原来这些痛点,人家早就考虑到了。