别再猜了!用‘树的后序遍历’法则,5分钟看懂Oracle执行计划里的执行顺序
用树的后序遍历法则破解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)
- 递归地后序遍历第一个子节点
- 递归地后序遍历下一个子节点
- 访问当前节点
将这一方法应用到我们的示例中:
- 从节点0(SELECT STATEMENT)开始
- 遍历其子节点1(FILTER)
- 遍历节点1的第一个子节点2(NESTED LOOPS)
- 遍历节点2的第一个子节点3(TABLE ACCESS FULL - EMP)
- 访问节点3
- 遍历节点2的下一个子节点4(TABLE ACCESS BY INDEX - DEPT)
- 遍历节点4的子节点5(INDEX UNIQUE SCAN - PK_DEPT)
- 访问节点5
- 访问节点4
- 访问节点2
- 遍历节点1的下一个子节点6(TABLE ACCESS FULL - SALGRADE)
- 访问节点6
- 访问节点1
- 遍历节点1的第一个子节点2(NESTED LOOPS)
- 访问节点0
因此,实际的执行顺序是:3 → 5 → 4 → 2 → 6 → 1 → 0
4. 实战演练:复杂执行计划解析
让我们通过一个更复杂的例子来巩固这一方法。考虑以下执行计划:
--------------------------------------------------------------------------------- | 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
- 访问节点3(DEPARTMENTS全表扫描)
- 遍历节点4
- 遍历节点5
- 遍历节点6
- 访问节点7(EMPLOYEES全表扫描)
- 访问节点8(EMP_DEPARTMENT_IX索引扫描)
- 访问节点6
- 遍历节点6
- 访问节点5
- 遍历节点5
- 访问节点4
- 访问节点2
- 遍历节点9
- 访问节点10
- 访问节点11(JOB_ID_PK索引扫描)
- 访问节点10
- 访问节点12
- 访问节点13(JOB_HISTORY全表扫描)
- 访问节点12
- 访问节点10
- 访问节点9
- 访问节点1
- 遍历节点2
- 访问节点0
最终执行顺序为:3 → 7 → 8 → 6 → 5 → 4 → 2 → 11 → 10 → 13 → 12 → 9 → 1 → 0
5. 常见执行计划模式解析
掌握后序遍历法则后,我们可以快速识别一些常见的执行计划模式:
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出现在"后面"感到困惑,认为索引没被使用。其实通过后序遍历分析,会发现索引扫描可能很早就执行了,只是在执行计划中的显示位置靠后而已。
