【Oracle】也来记一次慢SQL的优化
本文创意来自:微信公众号“占小狼的博客”21年9月13日博文 “SQL要快:记一次慢SQL优化”
下文将用到的表:
create table transfer( id int, from_account_id nvarchar2(20), to_account_id nvarchar2(20), amount int, free int, status int, comments nvarchar2(20), create_time timestamp default sysdate, update_time timestamp default sysdate, primary key(id));
给表填充两百万测试数据:
insert into transfer(id,from_account_id,to_account_id,amount,status,comments) select rownum, dbms_random.string('*',dbms_random.value(6,20)), dbms_random.string('*',dbms_random.value(6,20)), dbms_random.value(1,91000), dbms_random.value(1,12), dbms_random.string('*',dbms_random.value(6,20)) from dual connect by level<2000001;
然后先找点数据出来下面查询要用:
SQL> select status,to_account_id from transfer where rownum<10; STATUS TO_ACCOUNT_ID ---------- ---------------------------------------- 7 YWSRBVSVWUVKUNZRFNHE 7 IQNFNHLHQ 5 QBXOPTUDODVILMSCC 3 OQZDUIJ 6 SZSAGORE 4 UKFCUVCHULUKEQODLP 2 YAAFOMVO 10 UQHSEHGAUTNJBINHB 10 XRXJMDYP 已选择9行。
首先把原作者要优化的目标SQL拿出来跑一遍,现在是没有索引(除了主键索引)的情况。
无index查询 select id,from_account_id,to_account_id,amount,status,comments,create_time,update_time from transfer where status=10 and to_account_id='XRXJMDYP' and rownum<1500 order by amount desc,create_time asc 已用时间: 00: 00: 01.99
接近两秒,有点长,这还是Oracle数据库,换MySQL会更长。
现在打算给查询字段status,to_account_id和排序字段amount,create_time加联合索引,因为status的范围是1-12,按上面的筛法,取status=10的数据,那么无论其它条件如何选中的数据必然小于20%(1/12<0.2),那么加上的索引一定会用上的,不会出现所选数据大于两成而导致全表查询的情况发生。
create index idx_tf_4 on transfer(status,to_account_id,amount,create_time); 已用时间: 00: 00: 15.36
加索引花的时间不少。
再查一次看优化了多少:
有index查询 select id,from_account_id,to_account_id,amount,status,comments,create_time,update_time from transfer where status=7 and to_account_id='YWSRBVSVWUVKUNZRFNHE' and rownum<1500 order by amount desc,create_time asc 已用时间: 00: 00: 00.06
已经是秒出,看来索引确实奏效了。
让我们再把索引删除。
drop index idx_tf_4;
删除这个索引是因为不想让它影响下面的新查询效果。
先筛选后连接,先筛选后排序是基本的优化SQL思想,按这个思想我们可以把符合的记录找出再排序。
select id,from_account_id,to_account_id,amount,status,comments,create_time,update_time from transfer where id in (select id from transfer where status=10 and to_account_id='XRXJMDYP' and rownum<1500 ) order by amount desc,create_time asc 已用时间: 00: 00: 00.43
看,没加索引,只是用了id身上的固有索引,查询结果就是秒出,从此可以看出“先筛选后连接,先筛选后排序”的有效性。
前文说过Oracle CBO的强大,但CBO毕竟是机器,写出节省时间的SQL还需要人的积极参与。
现在秒出,再加个索引试试:
create index idx_tf_2 on transfer(status,to_account_id);
已用时间: 00: 00: 05.86
花的时间比四字段索引少三分之二,加完此索引后再查一次:
select id,from_account_id,to_account_id,amount,status,comments,create_time,update_time from transfer where id in (select id from transfer where status=5 and to_account_id='QBXOPTUDODVILMSCC' and rownum<1500 ) order by amount desc,create_time asc 已用时间: 00: 00: 00.17
加索引确实有效果。
删除此索引把前面的索引加上:
SQL> drop index idx_tf_2; 索引已删除。 已用时间: 00: 00: 00.06 SQL> create index idx_tf_4 on transfer(status,to_account_id,amount,create_time); 索引已创建。 已用时间: 00: 00: 09.87
再看查询效果:
select id,from_account_id,to_account_id,amount,status,comments,create_time,update_time from transfer where id in (select id from transfer where status=6 and to_account_id='SZSAGORE' and rownum<1500 ) order by amount desc,create_time asc 已用时间: 00: 00: 00.17
和加两字段索引差不多,这是因为idx_tf_2里的两字段也出现在了idx_tf_4中。
但是,加索引对插入更新的效果是负面的,换了SQL写法后已经是秒出,未必有再优化的必要,加了索引也只是在查询上锦上添花,插入更新是有损的,因此就本例来说,换新的先筛选后排序的SQL是最优解。
-END-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2017-09-13 【高中数学/不等式/数学归纳法/等比数列】证明伯努利不等式(1+h)^n>1+nh的三种方式
2017-09-13 【高中数学/等比数列/基本不等式】已知正项等比数列{an}满足a_4^2=a_m*a_n,则9/m+1/n的最小值为?
2017-09-13 【高中数学/等比中项/极值/基本不等式】已知a>0,b>0,9是3^a与27^b的等比中项,求:(a^2+2)/a+(3b^2+1)/b的最小值?
2017-09-13 【高中数学/极值/三角函数】已知x,y>0,那么根号下x+根号下y/根号下(x+y)的最大值为?
2017-09-13 【高中数学/极值/基本不等式】已知正实数x,y满足xy+2x+3y=42 则xy+5x+4y的最小值为?
2017-09-13 【高中数学/三角函数/数形结合】求f(θ)=|(3+2sinθ)/cosθ| 的最小值