










在Oracle数据库运维与SQL性能调优工作当中,SQL语句执行计划直接决定SQL运行的资源消耗、响应时间与业务吞吐量。生产环境中经常遇到相同SQL在统计信息变更、版本升级、索引增减之后发生执行计划突变,引发慢查询、数据库CPU冲高、业务接口超时等线上故障。风哥教程本文围绕Oracle SQL完整处理流程、软解析硬解析、绑定变量、游标、CBO优化器、访问路径、表连接方式、hint干预手段、执行计划查看、SPM基线管理、SQL Profile、SQL Patch等核心技术开展完整讲解。风哥 itpux‑com
本套风哥教程面向数据库运维工程师、DBA、后端开发人员,全部实验环境统一标准化配置:主机名称fgedu‑net‑cn,硬件规格为64G物理内存、8颗CPU,数据库实例名fgedudb,数据库名fgedudb,业务测试用户名fgedu,文件根目录统一替换为/fgedudb,整套实验基于Oracle19c企业版完成。风哥教程本文分为前言与大纲介绍、核心理论知识、实战操作演练、总结四大模块,实战章节包含大量可直接复制执行的SQL脚本、操作系统操作步骤,读者可以在测试环境完整复现全部实验现象,掌握执行计划阅读、计划异常排查、执行计划固化的全套生产运维手段。
本章节为本套风哥教程理论基础,只有理解底层理论,才可以读懂执行计划,定位线上SQL性能抖动根因。风哥教程 113257174
SQL调优的本质,是在硬件资源(CPU、内存、IO)约束之下,让SQL以最小化逻辑读、物理读、CPU消耗完成业务逻辑。一条SQL从客户端提交到数据库,到最终返回结果集,会依次经过语法解析、语义校验、库缓存查找、优化器计算、行源生成、执行返回结果整套链路。执行计划就是优化器输出的一套操作步骤,定义数据库以何种顺序读取表、索引,完成过滤、关联、排序、聚合运算。
生产环境会出现执行计划漂移:统计信息收集、数据库版本升级、系统参数变更、索引新增删除、数据分布倾斜,都会造成CBO生成完全不一样的执行计划。同一条业务SQL,计划发生变化之后,逻辑读可能提升数十上百倍,直接造成业务雪崩。
为了解决计划不稳定问题,Oracle自11g版本引入SPM(SQL Plan Management,SQL计划管理)基线框架,将经过验证性能优良的执行计划保存为接受基线,SQL再次运行优先选用已经验证的计划,同时支持后台安全评估接纳更优的新计划,兼顾计划稳定性与优化器迭代能力。网上搜索风哥教程可以学习全套数据库教程
当业务客户端向fgedudb实例提交SELECT、UPDATE、DELETE等SQL语句,数据库内部处理分为六大阶段:
fgedu用户是否具备对象访问权限,字段名是否合法;sql_id,在SGA共享池Library Cache查找是否已经存在解析树、父游标、子游标。如果找到匹配游标进入软解析流程;找不到匹配游标触发硬解析;游标(Cursor)是会话处理SQL的内存句柄。私有SQL区域存放于PGA内存;父游标、子游标存放在SGA共享池Library Cache当中。
绑定变量是抑制硬解析爆炸的核心手段,业务代码应当避免拼接字面量,使用:var绑定变量形式提交SQL。但是绑定变量也存在缺陷:当字段数据分布严重倾斜,不同传入绑定变量值,数据选择性差异巨大,同一条SQL需要完全不同的执行计划。为此Oracle引入bind‑sensitive绑定敏感游标、bind‑aware绑定感知游标,针对倾斜数据自动生成多个子游标适配不同输入值的最优计划。
理论重点:绑定变量可以抑制硬解析,但不能保证每次都输出最优执行计划,这也是生产环境需要SPM基线、SQL Profile介入调优的重要场景。
Oracle10g之后已经废弃RBO基于规则的优化器,全部默认使用CBO基于成本优化器。CBO做成本计算两大核心输入:对象统计信息、系统统计信息。
fgedu‑net‑cn(64G内存8CPU),系统统计信息会采集本机真实硬件IO与CPU指标,为成本计算提供硬件基准。统计信息过时、缺失、直方图丢失,会直接导致CBO计算出错误COST,生成劣质执行计划。很多线上SQL性能故障根源就是统计信息异常。
访问路径代表读取表数据的方式,分为全表扫描、索引范围扫描、索引唯一扫描、索引快速全扫描等。
驱动表也叫外部表,是表连接操作的输入源。嵌套循环中驱动表返回行数直接决定循环次数;哈希连接驱动表用来构建哈希桶。CBO依靠统计信息评估,选择行数少的表作为驱动表。统计信息失真会造成驱动表选择颠倒,SQL执行时间暴涨。
风哥数据库教程 itpux‑com
四者对比:hint写死在业务SQL;SQL Profile校正统计;SQL Patch后台追加hint;SPM基线直接保存整套执行计划,具备计划演化验证能力。
本套风哥教程全部实战操作,执行主机fgedu‑net‑cn,数据库fgedudb,业务用户fgedu,目录/fgedudb,硬件规格64G内存8CPU。
环境说明:操作系统用户oracle,登录服务器后设置环境变量,指向
fgedudb实例;数据库操作优先使用sysdba身份,业务测试对象使用fgedu用户。
登录操作系统oracle用户执行:
#确认主机名称
hostname
#确认oracle软件目录,全部使用/fgedudb
echo $ORACLE_HOME
#查看内存硬件 64G内存
free -h
#查看CPU 8CPU
lscpu
输出校验:hostname输出为
fgedu‑net‑cn;内存总大小64G;CPU总逻辑核8颗。
登录sqlplus / as sysdba,查看内存参数配置,64G物理内存主机,Oracle分配内存48G,预留内存给操作系统。
show parameter memory_target;
show parameter sga_target;
show parameter pga_aggregate_target;
show parameter processes;
show parameter optimizer_use_sql_plan_baselines;
show parameter optimizer_capture_sql_plan_baselines;
参考标准spfile参数(适配64G内存8CPU):
alter system set memory_max_target=48G scope=spfile;
alter system set memory_target=48G scope=spfile;
alter system set processes=800 scope=spfile;
--SPM基线默认开启
alter system set optimizer_use_sql_plan_baselines=TRUE scope=spfile;
alter system set optimizer_capture_sql_plan_baselines=FALSE scope=spfile;
说明:
optimizer_capture_sql_plan_baselines设置FALSE,不开启自动捕获基线;生产环境建议手工加载基线,避免系统自动捕获大量劣质计划进入基线库。
create user fgedu identified by fgedudb default tablespace users temporary tablespace temp;
grant connect,resource to fgedu;
--调优相关权限
grant select_catalog_role to fgedu;
grant administer sql management object to fgedu;
grant execute on dbms_xplan to fgedu;
grant execute on dbms_spm to fgedu;
grant execute on dbms_sqltune to fgedu;
grant execute on dbms_sqldiag to fgedu;
切换到fgedu用户,创建测试大表、索引,制造数据倾斜场景,用来复现执行计划漂移现象。
conn fgedu/fgedudb@fgedudb
--创建测试大表t_big
create table t_big(id number,code number,name varchar2(100));
--插入测试数据,制造数据倾斜,code=100 10万行,其他code各10行
declare
begin
for i in 1..100000 loop
insert into t_big values(i,100,'testdata'||i);
end loop;
for j in 200..300 loop
for k in 1..10 loop
insert into t_big values(100000+j*10+k,j,'test'||j);
end loop;
end loop;
commit;
end;
/
--创建普通B‑tree索引
create index idx_tbig_code on t_big(code);
--收集表统计信息,不收集直方图,人为制造统计信息缺陷
exec dbms_stats.gather_table_stats(ownname=>'FGEDU',tabname=>'T_BIG',method_opt=>'for all columns size 1');
上面method_opt size 1,直方图关闭,CBO无法识别code字段严重倾斜,会生成错误的执行计划,模拟线上统计信息缺失故障场景。
风哥教程 113257174
conn fgedu/fgedudb@fgedudb
explain plan for
select /*+ test_sql */ * from t_big where code=100;
select * from table(dbms_xplan.display());
dbms_xplan.display读取plan_table里面预估执行计划。注意:explain plan不会真实运行SQL,无法看到运行时统计信息,不能看到实际行号。
SQL已经真实执行,游标驻留在library cache,读取真实运行执行计划,包含A‑Rows实际返回行数,是生产最常用方式。
--执行目标SQL
select /*+ test_sql */ * from t_big where code=100;
--找到这条SQL的sql_id
select sql_id,sql_text from v$sql where sql_text like '%test_sql%';
--替换为实际查询得到的sql_id值
select * from table(dbms_xplan.display_cursor(sql_id=>'&sql_id',cursor_child_no=>0,format=>'ALLSTATS LAST'));
format参数说明:
ALLSTATS LAST:展示实际行、逻辑读、物理读等运行时指标;线上故障排查优先使用该格式。SQL已经从shared pool老化刷出内存,从AWR历史视图读取历史执行计划。
select * from table(dbms_xplan.display_awr(sql_id=>'&sql_id'));
COST是优化器估算成本;A‑ROWS是SQL实际返回行数;E‑ROWS是优化器预估行数。重要提醒:hint写死在业务SQL源码中,应用版本迭代会丢失,线上业务尽量优先使用SPM基线、SQL Patch,不要大规模依赖hint。
--使用index hint强制走索引访问路径
explain plan for
select /*+ index(t idx_tbig_code) */ * from t_big t where code=100;
select * from table(dbms_xplan.display());
hint常见错误:表别名写错,hint失效;hint语法拼写错误,Oracle不会抛出报错,直接忽略hint。
演示hint别名写错导致失效案例:
explain plan for
select /*+ index(idx_tbig_code) */ * from t_big t where code=100;
select * from table(dbms_xplan.display());
这里hint没有写表别名
t,hint失效,优化器不使用索引。很多开发DBA踩坑,hint无效却没有任何ORA报错。
本实验完整演示捕获基线、查看基线、演化基线、启用禁用基线、删除基线完整流程。
conn fgedu/fgedudb@fgedudb
select /*+ spm_test */ * from t_big where code=220;
--查询v$sql获取sql_id
select sql_id,sql_text,plan_hash_value from v$sql where sql_text like '%spm_test%';
记录返回的sql_id与plan_hash_value。
生产环境不建议开启自动捕获,推荐手工加载已经验证性能良好的执行计划进入基线库。
以sysdba身份执行:
conn / as sysdba
set serveroutput on
declare
v_ret number;
begin
v_ret := dbms_spm.load_plans_from_cursor_cache(
sql_id => '&input_sql_id',
plan_hash_value => '&input_phv',
enabled => 'YES',
fixed => 'NO'
);
dbms_output.put_line('加载基线成功数量:'||v_ret);
end;
/
select sql_handle,plan_name,enabled,accepted,sql_text
from dba_sql_plan_baselines
where sql_text like '%spm_test%';
字段说明:
ENABLED:是否启用该计划;ACCEPTED:是否为已经接受可用基线;SQL_HANDLE:SQL全局唯一标识,后续修改、删除基线需要使用该字段。人为重新收集统计信息,制造计划变更。
exec dbms_stats.gather_table_stats(ownname=>'FGEDU',tabname=>'T_BIG',method_opt=>'for all columns size auto');
再次反复执行目标SQL,优化器生成一套新的不同执行计划,新计划会加入基线,但是accepted=NO未接受,不会被数据库使用。
select /*+ spm_test */ * from t_big where code=220;
select sql_handle,plan_name,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like '%spm_test%';
执行演化任务,数据库后台测试未接受计划性能,判断是否接纳为可用基线。
declare
v_task_name varchar2(100);
v_report clob;
begin
v_task_name := dbms_spm.create_evolve_task(sql_handle=>'&input_sql_handle');
dbms_spm.execute_evolve_task(task_name=>v_task_name);
v_report := dbms_spm.report_evolve_task(task_name=>v_task_name);
dbms_output.put_line(v_report);
end;
/
执行完演化任务,读取报告,可以看到新计划性能对比结果,满足条件自动变为accepted=YES。
--禁用基线
declare
v_ret number;
begin
v_ret:=dbms_spm.alter_sql_plan_baseline(
sql_handle=>'&sql_handle',
plan_name=>'&plan_name',
attribute_name=>'ENABLED',
attribute_value=>'NO');
end;
/
--删除指定基线
declare
v_ret number;
begin
v_ret:=dbms_spm.drop_sql_plan_baseline(
sql_handle=>'&sql_handle',
plan_name=>'&plan_name');
dbms_output.put_line('删除基线数量:'||v_ret);
end;
/
SQL Profile用于校正优化器统计估算偏差,不需要修改业务SQL文本。需要ADMINISTER SQL MANAGEMENT OBJECT权限。
conn / as sysdba
VARIABLE tsk_name VARCHAR2(64);
BEGIN
:tsk_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_text=>'select * from fgedu.t_big where code=100',
scope=>'COMPREHENSIVE',
task_name=>'TUNE_TASK_FGEDU_01'
);
END;
/
--执行调优任务
exec DBMS_SQLTUNE.EXECUTE_TUNING_TASK(:tsk_name);
--接受生成SQL Profile,force_match=true,弱化SQL文本空格差异匹配
DECLARE
pro_name varchar2(60);
BEGIN
pro_name := DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(
task_name=>'TUNE_TASK_FGEDU_01',
force_match=>TRUE
);
dbms_output.put_line('生成profile名称:'||pro_name);
END;
/
--查询已存在SQL Profile
select name,signature,category,status from dba_sql_profiles;
--禁用SQL Profile
exec dbms_sqltune.alter_sql_profile(name=>'&profile_name',attribute_name=>'STATUS',attribute_value=>'DISABLED');
--删除SQL Profile
exec dbms_sqltune.drop_sql_profile(name=>'&profile_name');
SQL Patch后台给指定SQL附加hint集合,业务SQL源码无需改动,适合不方便修改应用程序的调优场景,调用包DBMS_SQLDIAG实现。
conn / as sysdba
declare
patch_name varchar2(128);
begin
patch_name := dbms_sqldiag.create_sql_patch(
sql_text => 'select * from fgedu.t_big where code=100',
hint_text => 'INDEX(T IDX_TBIG_CODE)',
name => 'FGEDU_PATCH_01',
description => '测试强制索引patch'
);
dbms_output.put_line('创建sql patch名称:'||patch_name);
end;
/
--查看sql patch
select name,sql_text,hint_text,status from dba_sql_patches;
--禁用patch
exec dbms_sqldiag.alter_sql_patch(name=>'FGEDU_PATCH_01',attribute_name=>'STATUS',attribute_value=>'DISABLED');
--删除sql patch
exec dbms_sqldiag.drop_sql_patch(name=>'FGEDU_PATCH_01');
当数据库迁移,需要把调优对象迁移到目标库,SPM基线可以通过staging中转表导出导入。
--创建基线中转存储表
exec dbms_spm.create_stgtab_baseline(table_name=>'SPM_STG_TAB',tablespace_name=>'USERS');
--源库:把基线导出到中转表
declare
v_cnt number;
begin
v_cnt:=dbms_spm.pack_stgtab_baseline(stgtab_name=>'SPM_STG_TAB',sql_handle=>'&sql_handle');
dbms_output.put_line('导出基线行数:'||v_cnt);
end;
/
--expdp导出SPM_STG_TAB表,传输到目标库impdp导入
--目标库:从中转表导入基线
declare
v_cnt number;
begin
v_cnt:=dbms_spm.unpack_stgtab_baseline(stgtab_name=>'SPM_STG_TAB');
dbms_output.put_line('导入基线行数:'||v_cnt);
end;
/
sql_id;dbms_xplan.display_cursor获取真实执行计划,对比E‑ROWS预估行数与A‑ROWS实际行数,判断统计信息是否失真;dba_sql_plan_baselines,确认是否存在基线;查看dba_sql_profiles、dba_sql_patches确认是否存在调优对象;本套风哥教程完整覆盖Oracle执行计划与基线管理整套知识,从SQL内部处理原理、软/硬解析、游标绑定变量、CBO优化器,到访问路径、表连接、hint使用,再到三大计划固化工具SPM基线、SQL Profile、SQL Patch理论与完整实操命令。风哥数据库教程 itpux‑com
optimizer_capture_sql_plan_baselines,避免大量劣质计划被捕获进入基线库;推荐手工加载经过业务验证性能优良的执行计划。整套实验所有脚本建议读者在测试环境完整复现,理解每一条命令的输出含义,再应用到真实生产运维工作。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。