3. 执行计划关键操作符解析

执行计划本质上是一棵由操作符(Row Source)构成的树。每个操作符负责从下层“取”一行数据,经过自身处理后向上层“交”一行数据。理解执行计划,核心不是背操作符名字,而是理解每个操作符从哪里取数据、如何取、代价花在哪里。本节围绕 TABLE ACCESS FULL、INDEX RANGE SCAN、INDEX UNIQUE SCAN、TABLE ACCESS BY INDEX ROWID、SORT ORDER BY、HASH JOIN、NESTED LOOPS、MERGE JOIN 这八类高频操作符,逐一拆解其含义与代价来源。

一、表访问类操作符:数据从哪里来

TABLE ACCESS FULL 表示对整张表做全表扫描。Oracle 从段的第一个区(extent)开始,按高水位线(HWM)以下的所有数据块顺序读取,逐块取出所有行,再根据 WHERE 条件过滤。它的代价来源是块读取量:无论过滤条件命中多少行,都要把 HWM 以下的块读完。因此当过滤条件选择性很差(命中比例高)或表很小时,全表扫描反而是最优选择;反之,若只取几行却扫了整张表,代价就明显偏高。判断依据可看执行计划中的 Rows(预估行数)与 Cost,以及是否出现 TABLE ACCESS FULL 配合大表。

INDEX RANGE SCAN 表示对索引做范围扫描。Oracle 先定位到索引 B 树中满足条件的起始叶块,然后沿叶块链表水平扫描,直到条件不再满足。它返回的是索引键值 + ROWID,而不是整行数据。代价来源是索引叶块扫描量 + 回表次数。范围越窄、命中索引条目越少,代价越低。典型场景是 WHERE deptno = 20 或 WHERE hiredate BETWEEN ...。

INDEX UNIQUE SCAN 是范围扫描的特例:当索引是唯一索引且条件包含全部索引列等值匹配时,Oracle 只需从根块走到叶块一次,定位到唯一一条索引条目即可。它的代价来源是B 树高度,通常为 2~4 次块访问,代价极低。例如 WHERE empno = 7369(empno 为主键)就会走 INDEX UNIQUE SCAN。

TABLE ACCESS BY INDEX ROWID 是回表操作。索引扫描只拿到 ROWID,若查询还需要索引中不存在的列,就必须用 ROWID 回到表中读取对应数据块。代价来源是回表次数 × 单次块访问成本。如果命中行数很多,回表会变成随机 I/O 的放大器,此时优化器可能放弃索引改走全表扫描。这也是“索引不是越多越好”的根本原因。

表访问路径:索引扫描与回表关系 索引 B 树 根块 → 分支块 → 叶块 索引叶块 键值 + ROWID INDEX RANGE / UNIQUE SCAN 按 ROWID 回表 表数据块 TABLE ACCESS BY INDEX ROWID 随机 I/O,命中行越多代价越高 TABLE ACCESS FULL 顺序读取 HWM 以下所有块 多块读,代价 = 块读取量 选择性差 / 小表时更优 代价判断要点 • 索引扫描代价 ≈ 叶块扫描量 + 回表次数;回表次数多时,优化器可能改选全表扫描 • 唯一索引等值查询走 INDEX UNIQUE SCAN,代价 ≈ B 树高度,通常 2~4 次块访问 • 覆盖索引(查询列全在索引中)可避免回表,是消除 TABLE ACCESS BY INDEX ROWID 的关键手段

二、排序类操作符:SORT ORDER BY

SORT ORDER BY 出现在需要按某列排序且无法借助索引天然有序时。Oracle 把下层传来的行放入排序区(Sort Area,属于 PGA),在内存中完成排序;若数据量超过 pga_aggregate_target 允许的排序区大小,就会溢出到临时表空间做磁盘排序。代价来源是排序行数 × 每行比较成本,以及可能的磁盘 I/O。若排序列上存在合适索引,Oracle 可能直接走索引避免排序;若排序后还要取前 N 行,常配合 SORT ORDER BY STOPKEY 提前终止。

三、连接类操作符:三足鼎立

NESTED LOOPS 是嵌套循环连接。驱动表(外层)每返回一行,就去被驱动表(内层)按连接条件查找匹配行。代价来源是驱动表行数 × 内层单次访问成本。它适合驱动表结果集小、内层连接列有索引的场景,能快速返回第一行,适合 OLTP。若驱动表行数很大,内层被反复探测,代价会急剧上升。

HASH JOIN 是哈希连接。Oracle 先读取较小的表(构建表),在 PGA 中按连接键建立哈希表;再扫描较大的表(探测表),逐行计算哈希值去哈希表中匹配。代价来源是两表扫描量 + 哈希表构建内存。它适合两表都较大、等值连接、且没有合适索引的场景,是数据仓库中最常见的连接方式。若哈希表放不下内存,会分区溢出到临时表空间。

MERGE JOIN 是排序合并连接。Oracle 先对两个表按连接键分别排序,然后像拉链一样归并扫描,匹配相同键值。代价来源是两次排序成本 + 归并扫描。它适合连接列上已有有序索引(可省去排序)、或非等值连接(如 <、BETWEEN)的场景。由于排序开销大,纯等值连接中通常不如 HASH JOIN 高效。

四、如何读执行计划中的这些操作符

看执行计划时,先看缩进层级:缩进越深越先执行,同级从上往下执行。再结合 Id、Operation、Rows、Cost 四列判断。例如出现 TABLE ACCESS BY INDEX ROWID 且 Rows 很大,说明回表次数多,应考虑加覆盖索引或改写 SQL;出现 HASH JOIN 且 Cost 高,应检查连接列是否有索引、统计信息是否准确;出现 SORT ORDER BY 且伴随磁盘排序,应评估是否能用索引消除排序。

操作符没有绝对的好坏,只有“在当前数据量、选择性、索引条件下是否合适”。同一个 SQL 在不同数据分布下,优化器可能选择完全不同的操作符组合,这正是执行计划需要结合统计信息和实际数据来解读的原因。

掌握这八类操作符的代价来源后,再看到执行计划时,就能从“这行是什么”进阶到“它为什么被选中、代价花在哪里、有没有更优路径”。这是后续索引设计、SQL 改写和统计信息调优的基础。