导读:本期聚焦于霓渡创作的《为什么SQL嵌套查询的执行计划在不同版本间差异很大?对比优化器版本改进》,敬请观看详情。同一条嵌套查询,在数据库版本升级后执行计划可能从毫秒级变为秒级,这并非SQL本身出了问题,而是优化器对子查询的改写策略和成本估算发生了显著变化。嵌套查询涉及IN、EXISTS、NOT IN等多种形态,优化器会将它们转换为半连接、反连接或物化操作,不同版本引入的新规则、调整的成本模型参数以及默认开关差异,都会让最终执行计划产生漂移。比如旧版本倾向先物化子查询结果再关联,新版本可能直接识别半连接并使用索引嵌套循环,逻辑等价但物理执行完全不同。理解这些差异有助于在升级前做好执行计划评估,利用计划基线、统计信息更新和参数调控降低性能风险。本文从嵌套查询的计划生成原理出发,对比典型优化器版本改进点,并给出可操作的应对建议。

同一条嵌套查询,在数据库版本升级后执行计划出现天翻地覆的变化,这个现象并不罕见。嵌套查询看似只是把子查询写在括号里,实际却牵涉优化器对半连接、反连接、子查询展开和物化等多种转换策略的选择。版本变迁带来新规则、新成本模型和默认参数调整,执行路径自然可能大不相同。

为什么SQL嵌套查询的执行计划在不同版本间差异很大?对比优化器版本改进

嵌套查询执行计划的基础转换

优化器处理嵌套查询的第一步通常是语法树改写。以内层IN子查询为例,优化器会尝试将IN改写为半连接,因为半连接只需判断驱动表记录是否在内层结果集中存在,不需要处理重复记录,这比普通内连接更高效。EXISTS、NOT EXISTS、NOT IN也有对应的半连接和反连接表示。

但改写不是无条件的。优化器必须判断改写后的连接顺序、连接算法和索引利用是否优于原始方案。当子查询中包含聚合、DISTINCT、LIMIT等复杂结构时,改写难度增加,优化器可能退回到物化方式,即先执行内层查询并把结果存入临时表。不同版本对改写条件的判定阈值和成本估算方法不同,直接导致计划选择不同。

下面这条查询是一个常见的嵌套查询例子。

SELECT e.employee_id, e.first_name, e.last_name
FROM employees e
WHERE e.department_id IN (
  SELECT d.department_id
  FROM departments d
  WHERE d.location_id = 1700
);

在旧版本中,优化器可能先执行子查询得到所有location_id为1700的部门编号,将它们物化到一个临时表,再让employees表与临时表做全表扫描关联。新版本则可能把子查询展开为半连接,直接对departments表按location_id索引取数,然后对employees表的department_id做探测。两种计划在数据量大时性能差距可达数倍。

优化器版本差异的根源:规则与成本模型演进

数据库优化器每次大版本升级都不是简单修补。开发团队会引入新的查询转换规则,例如子查询提升、半连接物化、派生表合并、公共子表达式消除等。新增规则会改变候选计划空间,原本没有的路径突然变得可用,优化器自然可能选择新的计划。

成本模型的变化同样关键。优化器根据统计信息估算每个候选计划的CPU、IO和内存代价,再选总代价最低者。不同版本可能调整代价权重,比如把随机IO的代价提高、把哈希连接内存代价调低。嵌套查询通常涉及多表关联,代价敏感度更高,一点参数变化就会让计划切换。

另外,统计信息收集策略也随版本变化。比如直方图类型增加、采样比例调整、自动收集触发条件改变。如果升级后统计信息没有及时重新收集,或者收集方式与旧版本不同,优化器得到的基数估算偏差会放大,最终导致执行计划漂移。默认优化器开关的变化也需要关注,有些数据库新增了类似semi-join、materialization之类的开关,默认开启后对嵌套查询影响巨大。

典型版本变化对比:子查询物化与半连接优化

为了更直观地理解差异,可以对比两个代表性版本对同一条NOT IN查询的处理。假设需要找出从未下过订单的客户。

SELECT c.customer_id, c.customer_name
FROM customers c
WHERE c.customer_id NOT IN (
  SELECT o.customer_id
  FROM orders o
  WHERE o.order_date >= CURRENT_DATE - INTERVAL '30' DAY
);

早期优化器缺少反连接改写能力,会老老实实执行子查询,把orders中所有符合条件的customer_id去重后物化成临时表,然后外层customers表逐行与临时表做NOT IN判断。这种实现方式存在两个问题:一是临时表可能非常大,二是NOT IN对NULL值敏感,当子查询结果包含NULL时整个查询结果可能为空,优化器为了正确性往往无法走索引。

新版本优化器会尝试将NOT IN改写为反连接,尤其是当优化器能够证明子查询结果不包含NULL或者用户明确语义为NOT EXISTS时,可以直接使用索引嵌套循环反连接。针对customers表每条记录,利用orders表上的customer_id索引探测是否存在匹配。只要找到一条匹配就跳过,找不到则返回该客户。这种执行方式不需要物化,内存占用低,返回首行速度快,非常适合OLTP场景。

可以从执行计划输出中看出端倪。旧版计划常出现Materialize和Full Table Scan,新版则出现Nested Loop Anti Join或Hash Anti Join。升级后如果发现计划从物化变为反连接,通常性能会变好;但也可能出现反面情况,比如新版本高估了某边基数,选择了哈希反连接导致内存争用,反而变慢。

如何应对跨版本执行计划漂移

升级数据库版本前,务必在测试环境采集重点SQL的执行计划基线。对于高并发核心业务的嵌套查询,尤其要对比升级前后的cost、行数估算和访问路径。很多数据库提供执行计划管理功能,例如计划基线或SQL计划管理,可以将旧版本中对生产已验证的执行计划导入到新版本并固定下来。

如果无法使用计划基线,还可以通过优化器提示或会话级参数做临时控制。例如关闭子查询展开、强制使用物化或半连接、指定连接顺序等。但这类提示会写死在SQL文本中,不利于后续维护,只适合作为过渡方案。

同时要重视统计信息的同步更新。升级后立刻重新收集涉及表及其索引的统计信息,避免因直方图变化导致基数估算失真。若数据库提供成本模型版本参数,应结合业务负载测试不同值的影响,选择与旧版本行为最接近的配置。最后,建立持续的性能监控,一旦发现执行计划漂移导致慢查询,能够快速回退或调整参数,确保升级平稳落地。

SQL嵌套查询执行计划优化器版本修改时间:2026-09-27 15:33:57

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0927/62610.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。