暂无图片
暂无图片
6
暂无图片
暂无图片
暂无图片

ORACLE | 偶发慢SQL优化案例

原创 淘气 2026-01-07
495

1、情况描述

2026新年第二个工作日,收到Oracle告警如下。

告警_x.jpg

看到以为只是简单的阻塞问题,排查后发现阻塞源头是一个执行了十几个小时还没结束的update操作。

与研发人员沟通后,得知该SQL是晚上8点半开始执行的批处理任务中的一个操作,正常情况下20分钟能执行完。这种情况之前也偶尔出现过,但之前没有造成什么影响就没有关注。不过这次已经影响到了正常的业务,所以先将该SQL kill掉,再进行排查。

SQL文本如下,因为保密和隐私,进行了表和字段的替换,sql_id为 73v0z2m8ypg8w。

update problem_table1 h set (h.stime,h.etime,h.clong,h.ctime)=(select t.stime,t.etime,t.clong,t.stime from problem_table2 t where h.cid=t.cid) where exists (select 1 from problem_table2 t where t.cid=h.cid) and substr(h.stime,0,10)=to_char(sysdate,'yyyy-mm-dd') and h.flag='1'

SQL的基本信息如下。

(1)problem_table1(别名 h):1亿行数据;

(2)problem_table2(别名 t):80万行数据;

(3)两表的关联字段cid有索引;problem_table1中有substr(h.stime,0,10)的函数索引;

2、排查过程

2.1、查看执行计划是否变化

因为该SQL之前跑着正常,这一次突然变慢了,所以首先怀疑是执行计划发生变化。

查询ASH,看执行计划更改时间。

select * from (select to_char(sample_time,'yyyy-mm-dd') as stime, sql_plan_hash_value from dba_hist_actice_sess_history ash) group by stime,sql_plan_hash_value order by 1;

输出结果如下。可以发现在2026-01-04日执行时,执行计划发生改变,与出问题的时间吻合。其中 926220318 是执行速度快的执行计划,415175573是执行速度慢的执行计划。

ash1.png

2.2、执行计划分析

查看SQL的2种执行计划。

select * from table(dbms_xplan.display_awr('73v0z2m8ypg8w'));

快plan:926220318

快plan_x.jpg

926220318 执行计划顺序:

4 —> 3 —> 5 —> 2 —> 7 —> 6 —> 1 —> 0

926220318 执行计划重点:

以problem_table1为驱动表,problem_table2为被驱动表,进行 nest loops semi连接。推测的伪代码如下:

declare cursor cur_problem_table1 is select cid from problem_table1 where substr(stime,0,10)=to_char(sysdate,'yyyy-mm-dd') and h.flag='1'; v_cid problem_table2.cid%type; begin for x in cur_problem_table1 loop select cid into v_cid from problem_table2; dbms_output.put_line(x.cid); end loop; end;

其中problem_table1谓词过滤后返回的结果集数量在20万左右,problem_table2的数据量则在80万左右。即对被驱动表进行20万次索引扫描。

慢plan:415175573

慢plan_x.jpg

415175573 执行计划顺序:

4 —> 3 —> 6 —> 5 —> 2—> 8 —> 7 —> 1 —> 0

415175573 执行计划重点:

415175573 以problem_table2为驱动表,problem_table1为被驱动表,进行 nest loops 连接。推测的伪代码如下:

declare cursor cur_problem_table2 is select cid from problem_table2; v_cid problem_table1.cid%type; begin for x in cur_problem_table2 loop select cid into v_cid from problem_table1 where substr(stime,0,10)=to_char(sysdate,'yyyy-mm-dd') and h.flag='1' and cid=x.cid; dbms_output.put_line(x.cid); end loop; end;

其中,problem_table2有 75万 的数据量,problem_table1有 1亿 的数据量。与926220318执行计划相比,驱动表的数据量本身就增加了近4倍,而被驱动表的数据量较之前剧增至1亿,且数据量这么庞大的被驱动表要被扫描80万次,性能自然骤降,这是415175573计划的SQL一直执行不出来结果的核心原因。

造成这种驱动表变化的最大可能原因是统计信息发生了巨变。

Oracle优化器认为以problem_table2为驱动表效率会更高。所以需要进一步查看统计信息是否准确。

2.3、查看统计信息是否过旧

select dts.owner, dts.table_name, to_char(dts.last_analyzed,'yyyy-mm-dd hh24:mi:ss') as last_analyzed, num_rows from dba_tab_statistics dts where dts.owner=upper('tq') and dts.table_name=upper('problem_table1')

检查后发现problem_table1、problem_table2两张表的统计信息都没有过旧,最近一次收集时间是2025-01-05 01:00 收集的,num_rows也和实际情况对得上。

继续检查近一段时间内,两张表的统计信息收集情况。

set linesize 200 pagesize 200 col owner format a12 col object_name format a24 col analyzed format a18 col rowcnt format 9999999999 col blkcnt format 99999999 select a.owner, a.object_name, to_char(b.ANALYZETIME,'yyyymmdd hh24:mi:ss') as analyzed, b.rowcnt, b.blkcnt from dba_objects a,wri$_optstat_tab_history b where a.object_type = 'TABLE' and a.object_name = upper('&table_name') and a.object_id = b.obj# order by 1,2,3;

输出结果如图所示。在图中我们可以看到problem_table2的统计信息基本平稳,但是在2026.1.3 收集的problem_table2表统计信息行数量是 0 !这是一个大问题,是导致 2026 .1.4 晚上批处理执行SQL时,SQL执行计划更改驱动表的直接原因。

统计信息_x.jpg

2.4、业务沟通

至于为什么 2026.1.3 problem_table2 的数据量是 0,这里在和业务研发方面进行沟通,终于真相大白:

Oracle数据库每天凌晨1点收集统计信息。

problem_table2 这张表只保留一天的数据量。在批处理任务中,先删除这张表所有数据,再插入数据,再执行这个sql_id为 73v0z2m8ypg8w 的SQL。
problem_table2 每天的数据量基本上是平稳的,但是遇到节假日、周末,problem_table2表的数据量很少甚至为0。
以这次为例,2026.1.4晚上20:30点执行批处理任务时,4号是工作日,当天的数据有70多万;但是统计信息此时还未更新,还是认为problem_table2表的数据量是0,Oracle 优化器认为以problem_table2为驱动表最优,此时执行计划变了,变成了最低效的执行计划。

3、解决方案

在排查清楚原因后,解决方案就比较清晰了:更改批处理任务执行时间或者绑定执行计划。在与研发人员沟通后,最终还是选择了绑定执行计划的解决方案。

使用sqlt工具进行执行计划绑定,该工具可以从MOS上获取。绑定执行计划步骤:

(1)上传sqlt压缩包并解压

(2)运行分析脚本

73v0z2m8ypg8w是目标SQL的sql_id

cd /home/oracle/sqlt/utl sqlplus / as sysdba @coe_xfr_sql_profile.sql 73v0z2m8ypg8wmh SQL_ID (required) PLAN_HASH_VALUE AVG_ET_SECS --------------- ----------- 926220318 xxx 415175573 xxx --根据提示,输入人工判断最优的执行计划 hash value Parameter 2: PLAN_HASH_VALUE (required) Enter value for 2: 926220318 --确认信息如下 Values passed: ~~~~~~~~~~~~~ SQL_ID : "73v0z2m8ypg8wmh" PLAN_HASH_VALUE: "926220318" --上述步骤完成后,会提示执行生成的 sql profile脚本 Execute coe_xfr_sql_profile_cdwjdd67x27mh_2979024279.sql on TARGET system in order to create a custom SQL Profile with plan 2979024279 linked to adjusted sql_text.

(3)运行输出结果中的sql脚本

@coe_xfr_sql_profile_cdwjdd67x27mh_2979024279.sql

(4)检查绑定情况

select name, SQL_TEXT, status from dba_sql_profiles;

4、总结反思

优化慢SQL时,SQL往往都已经执行结束或者被动结束了,此时的统计信息不能完全说明SQL执行时的真实情况。

Oracle 执行计划的突然改变往往和统计信息脱不了关系,对于统计信息的检查不能只着眼于最近一次收集情况,需要纵向观察近期内统计信息的收集情况。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论