Oracle数据库执行计划突变经常发生在统计信息收集、版本升级或索引调整之后,同一SQL稍微换一个绑定变量就可能从毫秒级响应变成分钟级。SQL Plan Management(SPM)通过基线机制约束优化器的选择范围,让执行计划变化处于可控状态。理解SPM需要先明确一个核心概念:基线里保存的是经过验证的、可接受的执行计划集合,而不是仅仅一条计划。

一、SPM基线捕获的两种主要方式
Oracle提供自动捕获和手动加载两种方式建立SQL计划基线。自动捕获由参数 optimizer_capture_sql_plan_baselines 控制,当该参数设置为 TRUE 时,系统会为重复执行的SQL自动创建基线,默认值为 FALSE。自动捕获虽然省事,但在高并发OLTP环境中容易生成大量基线对象,占用SYSAUX表空间并带来管理开销,因此生产系统很少全局开启。更常见的是会话级或针对某个维护窗口临时打开自动捕获,只对重点SQL进行采集。
手动加载通过 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE 或 LOAD_PLANS_FROM_SQLSET 完成。前者从共享池中的游标缓存直接提取当前执行计划,适合处理正在运行的SQL;后者从SQL调优集中批量导入,适合从测试环境或AWR快照中迁移计划。手动加载时可以指定SQL文本、SQL ID、解析模式等过滤条件,只选择目标SQL,避免无关计划混入。加载后的计划默认处于接受状态,优化器后续即可在这些已接受计划中按成本选择。
-- 开启自动捕获SQL计划基线
ALTER SESSION SET optimizer_capture_sql_plan_baselines = TRUE;
-- 手动从游标缓存加载基线
DECLARE
v_plans_loaded PLS_INTEGER;
BEGIN
v_plans_loaded := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
attribute_name => 'SQL_TEXT',
attribute_value => 'SELECT /*+ SPM_DEMO */ * FROM orders WHERE customer_id = :1'
);
DBMS_OUTPUT.PUT_LINE('Loaded: ' || v_plans_loaded);
END;
/
加载完成后,可以查询 DBA_SQL_PLAN_BASELINES 确认基线是否创建成功,关注 ACCEPTED 和 ENABLED 两个状态列。只有同时启用且被接受的计划才会进入优化器的选择范围。
二、新计划验证与基线演进
SPM的核心机制并不是锁死某一条执行计划,而是建立一组可接受计划。当SQL重新解析产生新的执行计划时,如果该计划不在基线中,优化器不会直接使用,而是将其捕获为未接受候选计划。此时需要执行演进任务,由数据库验证新计划是否比现有接受计划更优。验证过程会实际执行SQL并比较性能数据,如果新计划性能提升明显,则将其标记为接受,否则保持未接受状态。
演进可以通过 DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE 手动触发,也可以配置自动演进任务。手动演进的关键参数是 verify 和 commit,设置为 YES 时要求强制执行验证并自动提交结果。时间限制参数 time_limit 可以控制演进任务占用的时间窗口,避免在业务高峰期长时间消耗资源。
-- 演进指定SQL的基线,验证是否存在更优计划
SET SERVEROUTPUT ON
DECLARE
v_report CLOB;
BEGIN
v_report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(
plan_list => NULL,
time_limit => 60,
verify => 'YES',
commit => 'YES'
);
DBMS_OUTPUT.PUT_LINE(v_report);
END;
/
另一种严格的基线固定方式是设置 FIXED 属性。当某条基线被标记为固定后,优化器只会使用这条固定计划,即使存在其他成本更低的可接受计划也不会切换。这一特性适合用于已经反复验证且需要绝对稳定的核心交易SQL,但不建议大面积使用,否则会压制优化器对新统计信息和索引的适应能力。
三、基线的查询、修改与迁移
运维中需要频繁查看基线状态、修改属性以及在不同数据库之间迁移基线。DBA_SQL_PLAN_BASELINES 视图记录了SQL文本、计划名称、状态、执行次数等关键信息。通过 SQL_HANDLE 和 PLAN_NAME 可以唯一定位一条基线,这两个字段也作为后续修改操作的输入参数。
-- 查看基线概要信息
SELECT sql_handle, plan_name, enabled, accepted, fixed, executions
FROM dba_sql_plan_baselines
WHERE sql_text LIKE '%orders%';
-- 将某个基线设为固定状态,阻止其他计划被使用
DECLARE
v_result PLS_INTEGER;
BEGIN
v_result := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(
sql_handle => 'SYS_SQL_0123456789abcdef',
plan_name => 'SYS_SQL_PLAN_0123456789abcdef',
attribute_name => 'fixed',
attribute_value => 'YES'
);
END;
/
在迁移场景中,Oracle提供了 DBMS_SPM.PACK_STGTAB_BASELINE 和 UNPACK_STGTAB_BASELINE 两个过程。先将源库的基线打包到一张中间表,再通过数据泵或DB Link把表传输到目标库,最后解包还原。这样做可以保证测试环境验证过的稳定计划能够在生产环境复用,避免重新验证的时间成本。迁移时需要注意SQL文本和对象结构的一致性,如果目标库索引或分区缺失,基线计划可能无法重现。
日常维护中还应该关注 DBA_SQL_PLAN_BASELINES 中 LAST_EXECUTED 和 EXECUTIONS 字段的变化。长期没有执行记录的基线可以评估删除,而执行次数增长但没有性能回退的基线则保持原有状态即可。
四、常见误区与管理建议
SPM最常见的一个误区是把自动捕获全局打开,认为这样最安全。实际上自动捕获会在短时间内为大量SQL生成基线,其中很多SQL可能只执行一次或几次,后续不再使用。这些无用的基线会占用SYSAUX空间,并在每次SQL解析时增加优化器搜索基线的开销。更合理的做法是只对关键业务模块或高负载SQL手动加载基线,把基线数量控制在可管理的范围内。
另一个容易混淆的概念是SPM与SQL Profile的区别。SQL Profile 是SQL Tuning Advisor生成的辅助统计信息,用于纠正优化器基数估算偏差,它不保存执行计划,而是告诉优化器更准确的数据分布;SPM则直接管理执行计划集合。两者可以互补,但不要用SPM替代统计信息维护。如果统计信息长期不更新,基线计划也可能不是最优的,演进验证时应结合最新统计信息进行。
最后建议对基线进行定期评估,例如每月检查未接受基线的数量,执行演进任务,清理长期不使用的基线。同时监控 V$SQL 中执行计划哈希值与基线计划的关系,确认关键SQL实际使用的计划确实来自SPM。一个稳定的执行计划管理策略需要结合参数设置、手动加载、定期演进和容量监控,而不是简单开启某个开关就能一劳永逸。
Oracle SQL Plan ManagementSQL执行计划基线优化器稳定性修改时间:2026-10-03 15:14:08