数据库升级、参数调整、统计信息重新收集,这些操作之后SQL执行计划突变导致性能下降,是DBA和开发人员最常遇到的故障类型之一。传统的处理方式大多是加Hint或者调整统计信息,但这类做法维护成本高且不够系统化。Oracle从11g开始引入的SQL Plan Management(简称SPM)框架,为执行计划的稳定性提供了一套完整的解决方案,其核心组件就是SQL Plan Baseline(执行计划基线)。本文将系统讲解SPM的工作机制、配置方法和实战运维技巧。

SPM的工作原理:捕获、选择与演化三阶段
SPM的整体设计围绕三个环节展开。第一个环节是捕获(Capture),当优化器为某条SQL生成新的执行计划时,如果该SQL已经有基线存在,新计划不会直接投入使用,而是被记录为未接受的计划历史。第二个环节是选择(Selection),SQL被硬解析时,优化器会先按正常流程生成一个当前最佳计划,然后去基线库中查找该语句的已接受计划,如果当前计划不在已接受列表中,就会使用基线中成本最低的已接受计划来执行。
第三个环节是演化(Evolution),DBA可以定期验证那些未接受的计划,让优化器比较新旧计划的真实性能。如果新计划确实更优,就可以将其标记为接受,纳入基线。这种机制的好处非常明显:既防止了执行计划意外退化,又不阻塞真正更优的计划进入生产,实现了稳定性与最优性之间的平衡。
基线数据存储在数据字典中,具体来说位于SYS.SQLOBJ$、SYS.SQL$等基表之上,通过DBA_SQL_PLAN_BASELINES视图可以查询基线的详细信息,包括计划是否被接受(accepted)、是否被固定(fixed)、创建时间等关键字段。
基线的捕获方式与自动捕获配置
获取基线有两种主要途径:自动捕获和批量导入。自动捕获由参数optimizer_capture_sql_plan_baselines控制,默认为FALSE。开启后,当同一条重复执行的SQL出现新的执行计划时,系统会自动记录:第一个计划直接成为已接受基线,后续产生的不同计划则作为未接受的历史保留。
-- 开启自动捕获(注意:通常在会话级测试,全局开启需评估维护成本) ALTER SYSTEM SET optimizer_capture_sql_plan_baselines = TRUE; -- 确认使用已存在的基线(默认已开启) ALTER SYSTEM SET optimizer_use_sql_plan_baselines = TRUE; -- 查看已捕获的基线 SELECT sql_handle, plan_name, accepted, origin, created FROM dba_sql_plan_basines WHERE sql_text LIKE '%ORDERS%';
上面的查询里有一个拼写需要注意,正确的视图名是DBA_SQL_PLAN_BASELINES。origin字段反映了基线来源,AUTO-CAPTURE表示自动捕获,MANUAL-LOAD表示手动加载,AUTO-SQL-TUNE表示由自动SQL调优顾问创建。批量导入则适用于从AWR历史执行计划或SQL调优集中加载基线,这在数据库升级前的计划固化场景中尤其常用。
需要提醒的是,自动捕获开启后每条重复SQL都会维护计划历史,共享池和数据字典会有额外开销,因此生产环境更推荐的做法是:平时关闭自动捕获,在升级窗口前通过DBMS_SPM.LOAD_PLANS_FROM_SQLSET从AWR中定向加载Top SQL的基线。
DBMS_SPM实战:加载、演化与固定基线
手动加载基线是生产环境最常用的方式。下面的例子演示了如何从AWR快照中把指定SQL的执行计划加载为基线:
-- 第一步:创建SQL调优集并从AWR加载目标SQL
DECLARE
sts_name VARCHAR2(30) := 'UPGRADE_STS';
BEGIN
DBMS_SQLTUNE.CREATE_SQLSET(sqlset_name => sts_name);
DBMS_SQLTUNE.LOAD_SQLSET(
sqlset_name => sts_name,
basic_filter => 'sql_text LIKE ''%FROM ORDERS%'' AND elapsed_time > 1000000');
END;
/
-- 第二步:将SQL调优集中的计划加载为基线
SET SERVEROUTPUT ON
DECLARE
n PLS_INTEGER;
BEGIN
n := DBMS_SPM.LOAD_PLANS_FROM_SQLSET(
sqlset_name => 'UPGRADE_STS',
basic_filter => 'plan_hash_value = 1234567890');
DBMS_OUTPUT.PUT_LINE('加载的基线数量: ' || n);
END;
/
当优化器产生了新的候选计划,需要验证其性能是否优于现有基线时,就要执行演化操作。DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE会真实执行新旧计划(或基于性能数据分析)并输出报告,确认新计划更优后自动将其接受。
-- 演化指定SQL句柄的未接受计划
SET SERVEROUTPUT ON
SET LONG 10000
DECLARE
report CLOB;
BEGIN
report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(
sql_handle => 'SQL_7b76323ad3084f2c',
verify => 'YES', -- 真实执行验证
commit => 'YES'); -- 确认后提交
DBMS_OUTPUT.PUT_LINE(report);
END;
/
verify参数设为YES时系统会实际执行新旧计划并比较耗时,设为NO则直接信任新计划。commit为NO时只生成报告不修改基线状态,适合先观察再决策的场景。此外,fixed属性值得特别关注:被标记为fixed的基线拥有最高优先级,优化器只会从fixed计划中选择,其他已接受计划全部失效,这相当于给执行计划上了一把锁。代价是fixed基线会阻止演化机制生效,只有在明确要冻结计划时才建议使用。
基线与传统方案的对比及运维建议
和直接加Hint相比,SPM不需要修改应用代码,DBA可以独立完成计划固化,这在无法变更代码的第三方系统中优势明显。和旧的Stored Outline相比,基线与Outline Hint机制结合更紧密,随优化器版本升级自动兼容,且支持演化流程,而Outline在新版本中已不再增强。三者简单对比如下:
| 方案 | 是否改代码 | 计划更新能力 | 版本兼容性 |
|---|---|---|---|
| Hint | 是 | 静态,需手工调整 | 好 |
| Stored Outline | 否 | 不支持演化 | 逐渐废弃 |
| SQL Plan Baseline | 否 | 支持自动演化 | 随版本演进 |
日常运维中有几点经验值得参考。第一,定期检查DBA_SQL_PLAN_BASELINES中accepted为NO的记录数量,及时演化或清理,避免计划历史无限膨胀。第二,基线可以导出为staging表后在测试库和生产库之间迁移,用CREATE_STGTAB_BASELINE配合PACK_STGTAB_BASELINE完成,这是数据库升级前的标准动作。第三,删除基线时务必指定plan_name而不是只给sql_handle,否则会把该SQL的全部基线一次性删光,这是新手容易踩的坑。
总体而言,SPM提供的是一种防御性机制,它不能让差的SQL变好,但能保证好的计划不轻易变坏。配合SQL调优顾问、AWR报告形成完整的优化闭环,才能真正建立起稳定可预期的数据库性能管理体系。
SQL Plan Management执行计划基线Oracle优化器修改时间:2026-09-16 20:00:45