用树的后序遍历法则破解Oracle执行计划执行顺序之谜当你面对一个复杂的Oracle执行计划时那些层层嵌套的操作步骤是否让你感到困惑就像面对一棵枝繁叶茂的大树不知道从哪片叶子开始观察。本文将介绍一种直观的树的后序遍历方法帮助你在5分钟内理清执行计划的执行顺序。1. 为什么需要理解执行顺序执行计划是Oracle优化器为SQL语句设计的执行路线图但很多开发者只关注执行计划中各个操作的代价估算而忽略了操作之间的执行顺序。实际上理解执行顺序对于性能调优至关重要定位性能瓶颈知道哪个操作最先执行才能准确找到耗时最长的环节理解数据流向明确数据如何在各个操作之间流动有助于优化连接方式验证索引使用确认索引是否在预期的时间点被使用提示执行计划中的Id编号并不代表执行顺序这是许多初学者的常见误区。2. 执行计划的树形结构解析Oracle执行计划本质上是一棵树形结构我们可以通过以下特征识别这棵树缩进表示层级子操作相对于父操作有缩进Id编号唯一每个操作有唯一的Id但不表示执行顺序Operation列描述具体的操作类型以一个典型执行计划为例-------------------------------------------------- | Id | Operation | Name | -------------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | FILTER | | | 2 | NESTED LOOPS | | | 3 | TABLE ACCESS FULL | EMP | | 4 | TABLE ACCESS BY INDEX | DEPT | |* 5 | INDEX UNIQUE SCAN | PK_DEPT | |* 6 | TABLE ACCESS FULL | SALGRADE | --------------------------------------------------这对应的树形结构为SELECT STATEMENT (0) └── FILTER (1) ├── NESTED LOOPS (2) │ ├── TABLE ACCESS FULL - EMP (3) │ └── TABLE ACCESS BY INDEX - DEPT (4) │ └── INDEX UNIQUE SCAN - PK_DEPT (5) └── TABLE ACCESS FULL - SALGRADE (6)3. 树的后序遍历法则详解后序遍历是一种树形结构的遍历方法其规则是先遍历子节点再访问父节点。应用到执行计划中具体步骤为从根节点开始通常是SELECT STATEMENT递归地后序遍历第一个子节点递归地后序遍历下一个子节点访问当前节点将这一方法应用到我们的示例中从节点0SELECT STATEMENT开始遍历其子节点1FILTER遍历节点1的第一个子节点2NESTED LOOPS遍历节点2的第一个子节点3TABLE ACCESS FULL - EMP访问节点3遍历节点2的下一个子节点4TABLE ACCESS BY INDEX - DEPT遍历节点4的子节点5INDEX UNIQUE SCAN - PK_DEPT访问节点5访问节点4访问节点2遍历节点1的下一个子节点6TABLE ACCESS FULL - SALGRADE访问节点6访问节点1访问节点0因此实际的执行顺序是3 → 5 → 4 → 2 → 6 → 1 → 04. 实战演练复杂执行计划解析让我们通过一个更复杂的例子来巩固这一方法。考虑以下执行计划--------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000 | 156000 | | 1 | SORT GROUP BY | | 1000 | 156000 | | 2 | HASH JOIN | | 5000 | 780000 | | 3 | TABLE ACCESS FULL | DEPARTMENTS | 100 | 7200 | | 4 | VIEW | VW_DCL_5982B3F4 | 5000 | 675000 | | 5 | HASH GROUP BY | | 5000 | 155000 | | 6 | HASH JOIN | | 5000 | 155000 | | 7 | TABLE ACCESS FULL | EMPLOYEES | 1000 | 22000 | | 8 | INDEX FAST FULL SCAN | EMP_DEPARTMENT_IX | 5000 | 65000 | | 9 | MERGE JOIN | | 1000 | 117000 | | 10 | TABLE ACCESS BY INDEX ROWID | JOBS | 19 | 1786 | | 11 | INDEX FULL SCAN | JOB_ID_PK | 19 | | | 12 | SORT JOIN | | 1000 | 102000 | | 13 | TABLE ACCESS FULL | JOB_HISTORY | 1000 | 102000 | ---------------------------------------------------------------------------------按照后序遍历法则执行顺序如下从节点0开始遍历节点1遍历节点2访问节点3DEPARTMENTS全表扫描遍历节点4遍历节点5遍历节点6访问节点7EMPLOYEES全表扫描访问节点8EMP_DEPARTMENT_IX索引扫描访问节点6访问节点5访问节点4访问节点2遍历节点9访问节点10访问节点11JOB_ID_PK索引扫描访问节点10访问节点12访问节点13JOB_HISTORY全表扫描访问节点12访问节点9访问节点1访问节点0最终执行顺序为3 → 7 → 8 → 6 → 5 → 4 → 2 → 11 → 10 → 13 → 12 → 9 → 1 → 05. 常见执行计划模式解析掌握后序遍历法则后我们可以快速识别一些常见的执行计划模式5.1 嵌套循环连接(NESTED LOOPS)| 2 | NESTED LOOPS | | 3 | TABLE ACCESS FULL | EMP | 4 | TABLE ACCESS BY INDEX | DEPT | 5 | INDEX UNIQUE SCAN | PK_DEPT执行顺序3 → 5 → 4 → 2特点先访问外层表(EMP)然后对每一行通过索引访问内层表(DEPT)5.2 哈希连接(HASH JOIN)| 2 | HASH JOIN | | 3 | TABLE ACCESS FULL | DEPARTMENTS | 4 | TABLE ACCESS FULL | EMPLOYEES执行顺序3 → 4 → 2特点先完全访问两个表构建哈希表然后进行连接5.3 排序合并连接(MERGE JOIN)| 9 | MERGE JOIN | |10 | TABLE ACCESS BY INDEX | JOBS |11 | INDEX FULL SCAN | JOB_ID_PK |12 | SORT JOIN | |13 | TABLE ACCESS FULL | JOB_HISTORY执行顺序11 → 10 → 13 → 12 → 9特点先确保两个输入都已排序然后进行合并6. 工具辅助分析虽然手动分析执行顺序很有价值但借助工具可以事半功倍。以下是几种常用方法DBeaver执行EXPLAIN PLAN FOR [你的SQL]然后执行SELECT * FROM TABLE(dbms_xplan.display)可视化展示执行计划树SQL Developer使用F5快捷键生成执行计划图形化界面直观显示执行顺序命令行SET AUTOTRACE TRACEONLY EXPLAIN SELECT * FROM your_table;注意不同工具显示的执行计划可能在细节上有差异但树形结构和执行顺序原则是一致的。7. 性能调优实战技巧理解了执行顺序后可以应用以下调优技巧减少全表扫描早期出现的全表扫描影响最大优先考虑添加索引优化连接顺序确保较小的结果集先被处理识别低效操作如过早的数据排序或过滤验证索引使用确认索引在预期的时间点被使用例如如果发现一个全表扫描操作在很晚才执行可能影响不大但如果它出现在执行顺序的早期就需要重点关注。在实际项目中我经常遇到开发人员对执行计划中INDEX SCAN出现在后面感到困惑认为索引没被使用。其实通过后序遍历分析会发现索引扫描可能很早就执行了只是在执行计划中的显示位置靠后而已。