直接查AWR确认执行计划是否漂移:若7天内存在多个plan_hash_value则确已漂移,仅一个则问题不在执行路径;再用DBMS_XPLAN.DISPLAY_AWR核对谓词、基数及绑定值,RAC环境需跨实例加载基线并检查enabled/accepted状态。
直接查 awr,别猜原因——只要过去7天内有稳定快的 plan_hash_value,就能捞出来用。
怎么确认执行计划真变了?看 AWR 里有没有多个 plan_hash_value
执行计划“变差”不等于“变了”,得先验证是否真漂移。AWR 是唯一记录真实执行过的全量快照源,dba_sql_plan_baselines 只存手动或自动捕获的基线,没进过基线的“好计划”它根本不知道。
- 运行这个查询(把
'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 抖动、锁争用、内存压力)
-
plan_hash_value相同 ≠ 性能一致:还要查elapsed_time_total / executions_total是否同步飙升 - 注意:
dba_hist_sql_plan默认每 SQL 最多存 1000 行计划(受_cursor_plan_cache_threshold控制),高频 SQL 可能被截断,结果为空不等于没历史计划
怎么从 AWR 找出那个“真正快”的计划?重点看谓词和预估行数
光有 plan_hash_value 不够,得确认它在 AWR 里是完整、可用的——尤其要核对 access 和 filter 谓词是否合理,cardinality 预估是否贴近实际。
- 用
DBMS_XPLAN.DISPLAY_AWR查看具体计划:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR( sql_id => 'your_sql_id', plan_hash_value => 1234567890, db_id => 123456789, -- 从 dba_hist_sqlstat 查 format => 'BASIC +PEEKED_BINDS +NOTE' ));
- 关键检查点:
access是否命中索引字段、filter是否漏推到存储层、cardinality预估是否比实际大10倍以上 - 如果
format里不加+PEEKED_BINDS,看不到绑定变量实际值,容易误判谓词有效性 - 若提示 “no rows selected”,不是计划不存在,而是该
plan_hash_value在 AWR 中未记录完整访问路径(常见于并行计划或部分递归调用)
RAC 环境下加载基线必须跨实例操作,单节点 LOAD_PLANS_FROM_CURSOR_CACHE 会失败
在 RAC 中直接跑 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE 极其危险:它只读当前连接实例的 v$sql,而目标计划可能只在 node2 上活跃过,node1 上执行就返回 0 行。
- 正确做法是先用
DBMS_SQLTUNE.CREATE_SQLSET跨实例收集:
BEGIN
DBMS_SQLTUNE.CREATE_SQLSET(
sqlset_name => 'awr_sqlset_for_baseline',
description => 'SQLSET from AWR for baseline load'
);
END;- 再填充 SQLSET(需指定所有实例的
inst_id):
DECLARE
cur DBMS_SQLTUNE.SQLSET_CURSOR;
BEGIN
OPEN cur FOR
SELECT VALUE(p) FROM TABLE(
DBMS_SQLTUNE.SELECT_WORKLOAD_REPOSITORY(
begin_snap => 1000, end_snap => 1010,
basic_filter => 'sql_id = ''your_sql_id''',
attribute_list => 'ALL'
)
) p;
DBMS_SQLTUNE.LOAD_SQLSET(
sqlset_name => 'awr_sqlset_for_baseline',
populate_cursor => cur
);
END;- 最后统一加载基线:
DBMS_SPM.LOAD_PLANS_FROM_SQLSET,确保所有节点都拿到同一份计划 - 检查
dba_sql_plan_baselines.origin字段:必须是MANUAL-LOAD,AUTO-CAPTURE来的基线不可控,尤其当 RAC 各节点optimizer_capture_sql_plan_baselines设置不一致时
加载后为什么新计划还是没生效?检查 enabled 和 accepted 状态
基线写进去了不等于就被用了。Oracle 要求同时满足 enabled = 'YES' 且 accepted = 'YES' 才会强制匹配。刚加载的基线默认 accepted = 'NO',得手动 evolve 或设为 fixed。
- 查状态:
SELECT sql_handle, plan_name, enabled, accepted, fixed, origin FROM dba_sql_plan_baselines WHERE sql_id = 'your_sql_id';
- 若
accepted = 'NO',可立即固定(需ADMINISTER SQL MANAGEMENT OBJECT权限):
BEGIN
DBMS_SPM.ALTER_SQL_PLAN_BASELINE(
sql_handle => 'SQL_abc123',
plan_name => 'SQL_PLAN_abc123_1234567890',
attribute_name => 'fixed',
attribute_value => 'YES'
);
END;-
fixed = 'YES'后,优化器不会再选其他计划,但代价是后续演进(evolve)会被跳过——所以仅用于紧急恢复,长期要用还得走 evolve 流程 - RAC 下 evolve 必须在所有节点验证通过,否则会出现部分节点用新计划、部分节点卡在旧基线的情况
最常被忽略的一点:即使你从 AWR 找到了“看起来完美”的计划,它也可能因绑定变量窥探偏差、隐式类型转换或统计信息版本错配,在当前上下文里失效。不要只盯 plan_hash_value,一定回溯对应快照时间点的 db_id、绑定值、统计信息收集时间戳,三者缺一不可。


















