直接查ASH中SQL_PLAN_LINE_ID对应TABLE ACCESS FULL的操作可准确定位全表扫描在执行计划中的具体位置;需联合SQL_PLAN_OPERATION、SQL_PLAN_OBJECT_NAME等字段交叉验证,避免误判临时表或内部对象扫描,并结合DBMS_XPLAN.DISPLAY_AWR还原上下文分析优化空间。
直接查ASH里带TABLE ACCESS FULL的SQL_PLAN_LINE_ID
oracle 12c中,v$active_session_history(ash)每秒采样一次会话活动,其中sql_plan_line_id字段明确指向执行计划中某一行的操作编号。只要该行对应的操作是全表扫描,就能准确定位到执行计划里的具体位置,无需反复解析awr或手动比对plan_hash_value。
关键点在于:ASH记录的是“正在发生的”执行行为,不是历史快照;SQL_PLAN_LINE_ID和SQL_PLAN_OPERATION必须联合使用才有效——单独看ID没意义,它只是执行树里的一个节点序号。
- 执行计划中每个操作都有唯一
SQL_PLAN_LINE_ID,从0开始递增,根节点通常是0(SELECT STATEMENT),叶子节点可能是12、27等 -
SQL_PLAN_OPERATION值为TABLE ACCESS FULL时,其对应的SQL_PLAN_LINE_ID就是你要找的“全表扫描发生在线路几” - 注意区分大小写:
TABLE ACCESS FULL≠Table Access Full,查询时必须用大写匹配
用SQL_PLAN_LINE_ID反查完整执行计划结构
拿到SQL_PLAN_LINE_ID后,不能只看这一行就下结论。要还原上下文:它上面是谁(父节点)、下面挂了哪些子操作(比如FILTER、JOIN)、是否在嵌套循环里被多次调用。否则容易误判成“主表扫描”,实际是子查询里的临时结果集扫描。
推荐用DBMS_XPLAN.DISPLAY_AWR按SQL_ID和PLAN_HASH_VALUE拉出完整计划,再用LINE_ID列定位:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR( 'abc123xyz', NULL, NULL, 'ADVANCED +PEEKED_BINDS' ));
输出中id列即对应ASH里的SQL_PLAN_LINE_ID。重点关注该行的depth(缩进层级)、parent_id、operation和options(如STORAGE说明用了存储索引,PARALLEL说明开了并行)。
- 若
depth > 0且parent_id指向NESTED LOOPS或HASH JOIN,说明这是连接过程中的驱动表扫描,优化重点在连接顺序而非建索引 - 若
options含STORAGE,说明走的是Exadata智能扫描,物理读低但逻辑读高,不能简单等同于传统全表扫描 - 若同一
SQL_PLAN_LINE_ID在ASH里高频出现(COUNT(*) > 50),说明该节点是性能瓶颈热点,不是偶发抖动
为什么不用V$SQL_PLAN而优先选ASH
V$SQL_PLAN只保存当前缓存在Shared Pool里的执行计划,一旦游标老化、被刷出或被DBMS_SHARED_POOL.PURGE清理,就查不到。而ASH是内存中滚动保留的1小时(默认)采样数据,只要SQL刚跑过,哪怕已结束、游标已失效,也能回溯。
更关键的是:ASH能暴露“计划没变但行为突变”的情况。例如某SQL的PLAN_HASH_VALUE一直没变,但最近几天SQL_PLAN_LINE_ID = 8的TABLE ACCESS FULL行在ASH中出现频次翻了5倍——这说明不是执行计划变了,而是该表数据量/统计信息/绑定变量值发生了偏移,导致原本走索引的路径现在成本更高。
- 查ASH时务必加时间过滤:
SAMPLE_TIME > SYSDATE - 1/24(最近一小时),避免扫全量历史数据拖慢查询 - 不要依赖
SQL_IDalone:相同SQL_ID可能对应多个PLAN_HASH_VALUE,必须用(SQL_ID, SQL_PLAN_HASH_VALUE, SQL_PLAN_LINE_ID)三元组联合定位 -
V$SQL_PLAN适合验证“当前缓存计划长什么样”,ASH适合回答“过去一小时里,哪条SQL在哪一行真正干了全表扫描”
常见误判:SQL_PLAN_LINE_ID相同≠执行逻辑相同
同一个SQL_PLAN_LINE_ID在不同执行中可能代表完全不同的语义。比如SQL_PLAN_LINE_ID = 5在一次执行中是主表扫描,在另一次中可能是物化视图日志表的扫描,或WITH子句里临时结果集的扫描。
必须结合SQL_PLAN_OPERATION、SQL_PLAN_OPTIONS、SQL_PLAN_OBJECT_OWNER和SQL_PLAN_OBJECT_NAME四者交叉确认。尤其注意SQL_PLAN_OBJECT_NAME为空时,大概率是内部对象(如SYS_TEMP_XXX)或WITH子句别名,不是真实业务表。
- 若
SQL_PLAN_OBJECT_OWNER是SYS或NULL,跳过分析——这不是你的应用SQL问题,是Oracle内部操作 - 若
SQL_PLAN_OPERATION是TABLE ACCESS FULL但SQL_PLAN_OBJECT_NAME带$或_前缀(如TEMP_TABLE$_123),说明是临时表或物化视图刷新,不是索引缺失问题 - 最危险的是
SQL_PLAN_LINE_ID指向FILTER操作下的TABLE ACCESS FULL:表面看是全表扫描,实则是FILTER条件无法下推,需要先扫表再过滤,此时建索引无效,得改写SQL
真正难的不是找到SQL_PLAN_LINE_ID,而是判断这个ID背后有没有可干预的优化空间。很多情况下,你看到的全表扫描,其实是优化器在统计信息指导下做出的合理选择——比如小表、高并发短事务、或查询返回超过20%行数。强行加索引反而增加维护开销和锁竞争。











