Oracle执行计划变差了?利用AWR SQL Plan Baseline排查

作者:技术拾光

时间:2026-05-05

浏览:184

先查AWR确认SQL历史执行计划是否漂移:若7天内存在多个plan_hash_value则计划已变化,否则问题不在执行路径;再验证基线状态(enabled/accepted)、参数设置及SQL文本一致性。 为什么SQL执行计划突然变差了?先看AWR里有没有历史基线 Oracle优化器有时确实会“调皮

文章目录

先查AWR确认SQL历史执行计划是否漂移:若7天内存在多个plan_hash_value则计划已变化,否则问题不在执行路径;再验证基线状态(enabled/accepted)、参数设置及SQL文本一致性。

为什么SQL执行计划突然变差了?先看AWR里有没有历史基线

Oracle优化器有时确实会“调皮”一下。统计信息更新、绑定变量窥探,甚至是版本升级,都可能让它生成一个不那么理想的执行计划。但话说回来,并非所有的“变慢”都得归咎于计划本身。第一步,得先搞清楚:这条SQL过去有没有一个稳定可靠的好计划?要知道,dba_sql_plan_baselines视图里存放的,只是那些被手动加载或自动捕获的基线。而真正的“历史档案馆”,其实是AWR——dba_hist_sqlstat和dba_hist_sql_plan的组合,那里保存着所有SQL真实执行过的全量快照。

怎么查?关键看最近7天这条SQL的执行计划变化趋势:

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);
  • 如果返回了多个plan_hash_value,那没跑了,执行计划确实发生过漂移。
  • 如果只有一个,那问题大概率出在其他地方,比如I/O抖动或者锁争用,执行路径本身可能并非元凶。
  • 这里有个细节:plan_hash_value相同,不代表性能就一致。务必检查elapsed_time_total / executions_total这个比值,看看它是不是同步飙升了。
  • 另外,dba_hist_sql_plan默认只保留每条SQL最近的1000个执行计划(受_cursor_plan_cache_threshold这个隐含参数影响),对于高频执行的SQL,历史计划可能已经被截断了。

怎么把AWR里的好计划抓出来,变成SQL Plan Baseline?

想把AWR里的“黄金计划”固定下来?直接创建基线可不行。得先把它还原成可执行的SQL文本和完整的执行计划。核心思路是:先用DBMS_XPLAN.DISPLAY_AWR把计划“挖”出来,再用DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE或LOAD_PLANS_FROM_SQLSET把它“搬”进基线库。

具体操作,分三步走:

  • 首先,从dba_hist_sqlstat里,找到目标plan_hash_value对应的sql_id和dbid。
  • 接着,用DBMS_XPLAN.DISPLAY_AWR('sql_id', plan_hash_value, db_id)验证一下,确保这个计划包含了完整的访问路径。要特别留意filter、access谓词以及cardinality的预估情况。
  • 最后,执行下面的语句加载基线(需要具备ADMINISTER SQL MANAGEMENT OBJECT权限):
    BEGIN
      DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
        sql_id => 'your_sql_id',
        plan_hash_value => 1234567890,
        fixed => 'YES',
        enabled => 'YES'
      );
    END;

这里有个关键点:LOAD_PLANS_FROM_CURSOR_CACHE只能加载当前还在库缓存(v$sql)里的计划。如果那个“好计划”已经被老化出去了,就得换个法子——先用DBMS_SQLTUNE.CREATE_SQLSET从AWR里把计划导出到SQLSET,再调用LOAD_PLANS_FROM_SQLSET来完成加载。

基线生效了但SQL还是没走老计划?检查ACCEPTED和ENABLED状态

基线加载成功,就等于万事大吉了吗?未必。一个基线能不能被优化器选中,还得看两个关键状态:enabled(是否启用)和accepted(是否通过验证)。一个常见的坑是:手动加载时设置了fixed => 'YES',却忘了把enabled也设为'YES',结果基线静静地躺在那里,根本不参与计划选择。

怎么检查状态?用这个查询:

SELECT sql_handle, plan_name, enabled, accepted, fixed, origin
FROM dba_sql_plan_baselines
WHERE sql_text LIKE '%your_keyword%';
  • 如果enabled = 'NO',说明基线被禁用了,执行ALTER BASELINE ... ENABLE就能恢复。
  • 如果accepted = 'NO',意味着这个计划没有通过验证(比如自动演进任务失败了)。这时候,即便enabled = 'YES',它也不会被选中。
  • 字段origin也值得一看:'MANUAL-LOAD'表示人工导入,'AUTO-CAPTURE'则是自动捕获。后者默认accepted = 'YES',但fixed = 'NO'。

如果发现accepted = 'NO',但又想强制使用这个计划,可以执行DBMS_SPM.ALTER_SQL_PLAN_BASELINE把accepted状态改为'YES'。不过,这么做之前,务必确认这个计划在相似的数据量下确实有效——否则,就可能把一个更差的计划给固化下来。

为什么开了基线,执行计划还是漂移?留意OPTIMIZER_USE_SQL_PLAN_BASELINES参数

基线功能可不是“一劳永逸”的保险丝。实例级参数optimizer_use_sql_plan_baselines默认是TRUE,但如果它被显式地设为了FALSE(比如在PDB级别执行了ALTER SYSTEM SET optimizer_use_sql_plan_baselines=FALSE),那么基线功能就彻底失效了。

  • 检查当前值:SHOW PARAMETER optimizer_use_sql_plan_baselines
  • 在多租户环境下要特别注意:必须在目标PDB里确认这个参数,CDB$ROOT的设置不会自动继承给PDB。
  • 即便参数是TRUE,如果SQL里使用了像/*+ OPT_PARAM('optimizer_use_sql_plan_baselines' 'FALSE') */这样的优化器提示,也会绕过基线。
  • 还有一个本质限制:基线约束的是计划的“形状”(比如连接顺序、访问方法),它无法控制绑定变量具体值带来的谓词选择性变化。所以,即便在同一个基线下,不同的变量值仍可能导致cardinality估算错误,从而引发实际的性能波动。

最后,也是最容易被忽略的一点:基线绑定的是SQL文本的哈希值。这意味着它对空格、换行甚至注释都极其敏感。在应用里动态拼接SQL时,哪怕只是多了一个空格,sql_id就变了,基线自然也就匹配不上了。这才是关键所在。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章