执行计划漂移的铁证是同一sql_id在dba_hist_sql_plan和dba_hist_sqlstat中存在多个plan_hash_value;需通过查询分组统计、display_awr比对谓词与基数、检查基线跨实例状态等综合判断。

查 AWR 里有没有多个 plan_hash_value
执行计划“变了”不是靠肉眼感觉,而是看 dba_hist_sql_plan 和 dba_hist_sqlstat 里是否出现多个 plan_hash_value。一个 sql_id 对应多个 hash 值,才是漂移的铁证。
运行这个查询(把 'your_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);
- 返回多行 → 计划确实漂移过,继续往下查原因
- 只有一行 → 性能下降大概率和执行路径无关,先排查 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,但性能影响巨大。不能只依赖 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 是全局的,但基线是否启用、是否被接受,是按实例独立管理的。
- 查基线状态不能只看
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 - 单节点执行
LOAD_PLANS_FROM_CURSOR_CACHE在 RAC 下会失败,必须跨实例操作
真正难的不是查出 plan_hash_value 变了,而是判断哪一次变化对应的是“好计划”——它可能出现在三天前某个凌晨快照里,且没有被基线捕获,也没有留在当前库缓存中。这时候,DISPLAY_AWR 加 ADVANCED 格式输出的 Note 和谓词细节,就是唯一能交叉验证的依据。











