执行计划变差后,为什么不能直接改统计信息?
先说个大实话:很多人一遇到执行计划“漂移”、跑偏,第一反应就是——赶紧收集下统计信息吧。但这事儿真没那么简单,也未必管用。
说到底,核心原因无非两个:一是收集统计信息本身就很耗时,尤其对大表来说,跑一轮 dbms_stats.gather_table_stats 可能十分钟、半小时就过去了,可业务这会儿已经卡在那儿,你哪有时间等它跑完?二是——这也是更隐蔽的一点——问题可能根本就不是统计信息引起的。比如在 11g 环境中,你开了 optimizer_capture_sql_plan_baselines,结果新计划还没来得及验证就被自动启用了,这叫“计划漂移”,跟统计信息一毛钱关系都没有。19c 之后还有 optimizer_adaptive_features 这个变量在搅局,但更早的版本也一样有各种“坑”。

怎么从 AWR 中捞出历史好计划并导入 SPM?
AWR 本身其实不存完整的执行计划,它只存了一个 plan_hash_value 和 SQL 文本。真正能导出可复用计划的,是藏在 AWR 快照背后的 DBA_HIST_SQL_PLAN 和 DBA_HIST_SQLSTAT。
这里有两个工具可以用:一个是 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE,另一个是更稳妥的 DBMS_SPM.LOAD_PLANS_FROM_AWR。但注意,后者在 11.2.0.2 及以上版本才支持,而且有个硬前提——这个 SQL 在 AWR 快照周期内必须确实被采样过。也就是说,如果好计划只出现过一次,恰好没被 AWR 抓到,那就没法用这个函数捞出来。
具体怎么做?三步走:
- 先查出目标 SQL 的
sql_id:SELECT sql_id, sql_text FROM v$sql WHERE sql_text LIKE '%your_condition%'; - 确认该
sql_id在 AWR 中有历史计划:SELECT DISTINCT plan_hash_value FROM dba_hist_sql_plan WHERE sql_id = 'xxx'; - 最后,用
DBMS_SPM.LOAD_PLANS_FROM_AWR把那个好计划的plan_hash_value导进来:
BEGIN
DBMS_SPM.LOAD_PLANS_FROM_AWR(
begin_snap => 12345,
end_snap => 12346,
basic_filter => 'sql_id = ''abc123'' AND plan_hash_value = 1234567890'
);
END;
导入后计划没生效?检查这几个硬条件
很多人以为把计划导入 SPM 就完事了,结果发现新计划根本没生效。这背后有几个经常被忽略的“硬门槛”:
- 参数
optimizer_use_sql_plan_baselines必须为TRUE。虽然默认就是 TRUE,但有些环境为了排查问题被人为关掉了,这点得确认。 - SQL 文本必须完全一致。注意,不是“差不多一致”——尾部空格、换行符、大小写差异,都会导致匹配失败。这真是最烦人的坑之一。
- 如果 SQL 使用了绑定变量,而历史计划是在不同绑定值下生成的,且
force_matching模式没开启,那 SPM 也可能不匹配。 - 执行 SQL 时如果加了
/*+ NO_SQL_PLAN_BASELINE */提示,那会直接绕过基线,计划自然不生效。
coe_load_sql_profile.sql 和 SPM 基线的区别在哪?
有些 DBA 会用 coe_load_sql_profile.sql 来“打补丁”——它本质是创建一个基于 HINT 注入的 SQL Profile,优先级比 SPM 基线还高。但这才是个大问题:Profile 属于“补丁式”修复,是会话级或语句级的 hint 封装,不参与计划演化,也不支持自动验证和演进。而 SPM 基线是存储在 sys.sqlobj$ 等正式对象中的计划控制机制,它能自动验证、演进和禁用,是更正规的路子。
所以建议是:生产环境优先走 SPM。Profile 只用于紧急兜底,或者跨版本迁移这种特殊场景——毕竟,你总不能让对方改 SPM 配置吧。
还有个容易被忽略的点:AWR 快照间隔默认是 60 分钟。如果好计划只在某次短时负载中间出现过,但没被 AWR 采样到,那 LOAD_PLANS_FROM_AWR 就查不到它。这时候只能退而求其次,从 v$sql_plan 或者 cursor cache 里抓,或者依赖之前手动捕获的 baseline。总之,未雨绸缪永远比事后补救省心。
