判断执行计划是否发生漂移,最直接的证据是同一个 sql_id 在 dba_hist_sql_plan 与 dba_hist_sqlstat 中对应了多个 plan_hash_value;实际排查时,通常还要结合分组统计、DISPLAY_AWR 对比谓词与基数估算,以及检查基线在跨实例环境中的生效状态,才能做出准确结论。

先查 AWR 中是否存在多个 plan_hash_value
判断 Oracle 执行计划有没有“变”,不能凭感觉或单次执行现象,而要看 dba_hist_sql_plan 和 dba_hist_sqlstat 里是否出现了多个 plan_hash_value。同一个 sql_id 一旦对应多个 hash 值,基本就可以认定发生过执行计划漂移。
可以先执行下面这条 SQL(将 'your_sql_id' 替换为实际的 SQL ID):
SELECT plan_hash_value, COUNT(*), MIN(sample_time), MAX(sample_time) FROM dba_hist_sql_plan p JOIN dba_hist_sqlstat s USING (sql_id, plan_hash_value) WHERE sql_id = 'your_sql_id' AND sample_time > SYSDATE - 7 GROUP BY plan_hash_value ORDER BY MIN(sample_time);
- 如果返回多行 → 说明该 SQL 的执行计划确实发生过变化,下一步继续分析原因
- 如果只返回一行 → 性能变差大概率不是执行路径切换导致,建议优先排查 I/O、锁等待、内存压力等问题
dba_hist_sql_plan默认对每个sql_id最多保留 1000 行计划记录,高频 SQL 可能出现截断;因此查不到结果,并不代表历史上没有执行计划
用 DBMS_XPLAN.DISPLAY_AWR 检查谓词和预估行数是否异常
即使 plan_hash_value 没变,也不代表执行效果一定一致。比如统计信息陈旧或分布失真时,cardinality 的预估可能严重偏差(例如预估 100 行,实际却返回 10 万行),进而引发嵌套循环放大、临时表空间占满、响应时间陡增等性能问题。
为了看到真实绑定变量取值以及基线使用情况,建议必须带上 +PEEKED_BINDS 和 +NOTE 参数:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR( sql_id=> 'your_sql_id', plan_hash_value => 1234567890, db_id => 123456789, format=> 'BASIC +PEEKED_BINDS +NOTE' ));
- 重点关注
access是否真正命中了索引列,filter是否没有成功下推到存储层 - 如果
cardinality的预估值和实际值相差 10 倍以上,通常就可以怀疑是统计信息不准确或失真 - 如果不加
+PEEKED_BINDS,就无法看到绑定变量的真实值,判断谓词选择性时很容易出现误判 - 出现 “no rows selected” 并不一定表示执行计划不存在,也可能是并行执行计划或递归调用信息没有被完整记录下来
逐行比对 dba_hist_sql_plan 中的 OPERATION 和 PREDICATES
plan_hash_value 本质上只是一个哈希标识,某些结构上的细微变化(例如谓词下推位置调整、索引扫描由唯一扫描变成范围扫描)未必会改变 hash,但对 SQL 性能的影响可能非常明显。因此,不能只依赖 hash 值来判断执行计划是否“真的变了”。
要进一步定位变化发生在哪一步,最好按快照做对比:
SELECT sql_id, plan_hash_value, snap_id FROM dba_hist_sqlstat WHERE sql_id = 'your_sql_id' AND snap_id IN (12345, 12346) ORDER BY snap_id;
- 拿到两个快照对应的
plan_hash_value后,再分别使用DBMS_XPLAN.DISPLAY_AWR导出完整执行计划,进行文本 diff 对比 - 重点比较字段包括:
OPERATION、OPTIONS、OBJECT_NAME、ACCESS_PREDICATES、FILTER_PREDICATES - 一个常见误区是:AWR 报告首页的 “Top SQL” 表格通常只展示当前快照里的 hash,无法直接看出历史上的执行计划漂移情况
在 RAC 环境下必须跨实例确认 SQL Plan Baseline 是否真正生效
在 RAC 集群中,同一个 sql_id 在不同实例节点上可能采用不同执行计划,即使显示出的 plan_hash_value 完全相同也不能掉以轻心。AWR 中的 plan_hash_value 是全局视角,但基线是否启用、是否被 accepted,往往需要按实例分别确认。
- 检查基线状态时,不能只看
DBA_SQL_PLAN_BASELINES,还应结合gv$sql验证每个inst_id上是否真正使用了目标基线 - 可通过
DBMS_XPLAN.DISPLAY_AWR(..., 'ADVANCED')查看 Note 信息,确认是否出现SQL plan baseline used LOAD_PLANS_FROM_CURSOR_CACHE只能加载当前仍存在于库缓存中的计划;如果目标好计划已经老化掉,就需要先从 AWR 提取,再导入 SQLSET- 在 RAC 环境中,仅在单节点执行
LOAD_PLANS_FROM_CURSOR_CACHE往往无法满足需求,必须按跨实例方式处理
真正麻烦的地方,往往不只是确认 plan_hash_value 发生过变化,而是要进一步找出:到底哪一次变化,对应的才是那个性能稳定的“好计划”。很多时候,它可能藏在三天前某个凌晨的 AWR 快照里,既没有被 SQL Plan Baseline 收录,也早已不在当前库缓存中。到了这种场景,最有价值的交叉验证手段,往往只剩下结合 DISPLAY_AWR 与 ADVANCED 格式输出中的 Note、谓词细节以及行数估算信息,逐步还原执行计划变化的真实过程。
