提到SQL优化,咱们不得不学习几个名词,这就比如武侠小说里练习武功同样,不知道几个穴道,不了解几个气门都很差意思提本身是练功夫的。名词挺多,除了这里提到的还有好多,好比内外链接、嵌套循环、游标共享、绑定变量、软硬解析等等,武功太多,练不过来,那咱们先把马步扎好再说。没有提到的功夫在藏经阁都有,请按需百度。sql
2.1 执行计划数据库
执行计划是什么呢?就是SQL执行的路径和执行步骤。怎么理解呢?你从家里到公司能够选择开车走高速,也能够选择作公交车走市区街道,那么你选择用哪一个交通工具,走哪一个路线,就是你的执行计划。可是一次只能有一个方案。函数
一个SQL生成了执行计划,他就会被固定到共享池里,只有在表发生了变动、统计信息过旧、共享池被刷新、数据库重启等状况下SQL才会从新生成执行计划。仍是从家到公司,城市在修路,公交路线变化都会影响你选择什么样的方式上班。工具
2.2 索引性能
索引是一个单独的、物理的数据库结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。怎么理解呢?表是一本书,索引就是这本书的目录。学习
2.3 统计信息测试
统计信息主要是描述数据库中表,索引的大小、规模、数据分布情况等的一类信息。好比表的行数、块数、平均每行的大小、索引的leaf blocks、索引字段的行数、不一样值的大小等,都属于统计信息。优化
Oracle的基于成本的优化器(还有一种是基于规则的,武功过期了,不练了)正是根据这些统计信息数据,计算出不一样访问路径下,不一样链接方式下,各类计划的成本,最后选择出成本最小的计划。若是没有统计信息呢?会发生动态采样,就是在生产执行计划前去表、索引去进行数据采样。url
怎么理解呢?仍是从家到公司,有了地图和公交信息,高速收费状况等信息,你们就能够选择最合适的上班方案了。没有这些现成的信息你就须要实际去调研一下了,比较麻烦,并且可能形成不合适的选择。spa
3.某个SQL是否能优化?
怎么能肯定一个SQL是否能优化呢?这个不太好回答,DBA们常常说先分析分析看看。那么分析什么呢:如今效率如何?执行计划?跑的时候等待事件?表多大?选择列上有无索引?选择性如何?有统计信息?链接方式?......又是这些名词,尚未彻底理解的小伙伴不要着急,我不是卖野药的,立刻就要表演啦!
其实呢,也能够不用这么复杂。一个很是劣质的SQL(跑几十分钟,甚至是几个小时的SQL)最终能不能有质的提速,主要就是看业务的本质上须要访问多少数据量,若是业务本质上须要访问的数据量越少,通常来说提速余地就越大。简单吧,因此一个业务专家,你遇到了一个劣质SQL,不用DBA分析,你应该就知道有没有优化余地了。
怎么理解呢?一个大操场上有一个红色的玻璃球,让你去拿到,你没看见,地毯式的找半天,有人给你个坐标图,你10秒钟跑过去就找到了,这就是有提速余地,若是满操场上都是须要的红色玻璃球让你拿,不管怎么拿,都要拿半天,这就是无法有质的飞跃(就是没办法几秒钟、几分钟把活干完)。
下面咱们按部就班的讲一些案例吧。
少商剑:合理利用索引
1.1利用索引提升效率
INS_USER_571 表上有两个索引IDX_INS_USER1_571,IDX_INS_USER2_571分别对应列BILL_ID,EXPIRE_DATE列,SQL 和执行计划:
select * from SO1.INS_USER_571 M where M.BILL_ID = '13905710000' and M.EXPIRE_DATE > sysdate;
符合BILL_ID的数据只有一条,而符合EXPIRE_DATE条件的数据有几万条,就是说BILL_ID选择性好,选择IDX_INS_USER1_571索引就会更快的找到这条数据。
怎么理解?大操场上找1个玻璃球,给你两个坐标图,一张直接告诉你这个玻璃球的具体位置,一张图告诉你在某个10米的范围圈内,选择哪一个呢?确定选第一张。
1.2走索引反而慢?
在CREATE_DATE上创建个索引IDX_INS_USER4_571:
SQL:
SELECT COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';
PLAN:没有走索引?!
难道是优化器有问题?测试下就知道了。为了明确加Hints测试:
SELECT /*+full(M)*/COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';
SELECT /*+INDEX(M,IND_INS_USRE4_571)*/COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';
看来优化器没有错!
当须要扫描的数据量较大时,走索引的成本比全表扫描的成本还要高。
怎么理解?一本书1000页,其中800页的内容都是你须要的,那你看书的时候就没有必要每看一页都要回过头来翻翻目录了,这样多罗嗦呀!直接顺序往下读好了。
1.3本招的几个口诀
在where条件中选择列上加了函数,无法利用索引
例如:where to_char(create_date,’YYYY-MM-DD HH24:MI:SS’)>= ‘2015-04-08 00:00:00’
这样无法利用到create_date的索引
正确的写法:
where create_date>=to_date( ‘2015-04-08 00:00:00’,’YYYY-MM-DD HH24:MI:SS’)
案例:select count(t.visitor_id) as pv
from t_stat_logbase t
where instr(t.visit_url, :""SYS_B_0"")>:""SYS_B_1"" and
to_char(t.visit_date,:""SYS_B_2"")=:""SYS_B_3""
visit_url, visit_date 都有索引,并且visit_url列的选择性很是好,可是由于在本列上加了instr的函数,形成只能全表扫描,因天天零点左右执行频率高,数据库常常出现性能告警,应用改造后有效利用了索引,数据库恢复正常。
Ø 变量类型要正确,避免隐式转换形成无法利用索引
Bill_Id 是varchar2的类型
Where bill_id=1 就无法利用索引。
Ø Where条件终须用到前导列才能走上组合索引,组合索引中选择性好的要放在前面:
索引ind_ins_user5_571(create_date, cust_type): where cust_type =1
只有cust_type这个条件,而没有create_date走不上索引ind_ins_user5_571
Ø 在多表关联的状况下,即便表很小,在关联字段上加索引每每都很是有效,可能影响表的链接方式而节约成本。
Ø 不要试图每个字段上都加索引,索引越多对表的DML影响越大,对DML要求很高的接口表都不建议加索引,组合索引的列越多,索引树可能越深,有时候不但不能查询提速,反而还影响了DML效率。
例如:Where条件中有一个选择性很是好的字段,如BILL_ID/ACC_ID/USER_ID,其余选择条件如STATE/TYPE没有必要再进行组合索引。
1.4老板不再用担忧个人数据积压啦
某报表库下有这样一条sql,每一个地市启动了10个线程,去进行数据处理,每一个地市执行频率每小时高达几万次,形成数据库CPU压力巨大,而下降线程又会出现数据积压,咋整呢?DBA们也捉急呀!
select ROWID, t.*
from CHK_ORD_INTERFACE_574 t
where mod(INTERFACE_ID, :"SYS_B_0") = :"SYS_B_1"
and SCAN_STATE = :"SYS_B_2"
and rownum <= :"SYS_B_3"
sql特色: 执行计划走全表扫描,过滤条件SCAN_STATE字段只有2钟状况(0,1是否已稽核)
业务特色:CHK_ORD_INTERFACE_NN表从营业端经过dsg同步到报表端,在报表端进行数据稽核,稽核过的数据进行delete,本表按日进行分区,也就是碎片重用率很低,形成本表碎片极为严重,碎片率达99%以上,因dsg同步是经过rowid进行,形成本表不能经过move,shrink等手段进行碎片清理。走不上分区,过滤条件选择性低!不能整理碎片!看起来这不是没办法嘛?!
方案:本表的碎片率高达99%,说明实际的数据量很是少,能够建立全局索引(本表的生命周期是永久保留,全局索引在本业务特色下能够提升索引碎片的重用率,有效控制索引大小),数据块虽然不少,索引很小呀,创建,有效!原来1.4S,建立索引后0.002s,线程调整到1,照样完成任务,CPU也下去了,老板不再用担忧个人数据积压啦!
中冲剑:合理的逻辑
1.1 有效利用链接,避免嵌套
表的外链接通常都会比半链接效率上高的多,在保证业务效果的状况下,咱们更建议表关联代替IN/EXISTS子查询,好处多多,关系到驱动表啦,链接模式啦,这里不细表。有兴趣,请实践,有疑问,喊DBA!
在某报表系统的专项优化中,咱们抓到了一个异常复杂的sql涉及到13张表,5层的嵌套....经过分析业务需求以后,发现用多表关联能够代替In字查询(业务效果不变)咱们将SQL里面的三个小段落作了相似以下修改,并在关联键上建立了索引,由原来跑2个小时提速到10秒以内:
修改成:
1.2 括号内外差异很大
直接上菜:
原sql
问题出在哪里呢?本sql缓慢的根源是括号,括号犯了什么罪?他形成了针对RP_BUSI_DETAIL_NNN(571 180G)进行全表扫描,而业务特色是读取distinct customer_order_id, SALER_ID 且与RPT.INSX_OFFER_PRG_577 的ustomer_order_id列进行关联。
方案:建立customer_order_id, SALER_ID的组合索引(两列都非空),并将RPT.INSX_OFFER_PRG_NNN表的其余过滤条件放进子查询中,这样sql执行计划扫描索引,并且不进行回表。
效果:这个sql在1小时快照周期内就没有完成过,不过优化后就好啦,在不一样的条件范围内1-30秒均可以完成。
新sql:
1.3 深刻理解业务,利用好分区
很惋惜,上个故事尚未结束,过了一个星期,业务侧反映有些业务很快了,但是有些却很慢,特别是一次性统计3个月的报表根本就跑不了......有些快?有些慢?这个是重点,当即联系业务侧和开发确认快和慢的场景,并进行了跟踪,发现快的场景下都输入了ORG_ID条件,而慢的场景下都没有ORG_ID条件,那么关键点就在这里了!
不带条件ORG_ID = :ORG_ID,某月份的数据为例,那么符合条件的INSX_OFFER_PRG_571的数据有14万之多,与RP_BUSI_DETAIL_NNN表关联时 CBO优化器只会选择hash jion链接,且选择的索引只能是done_date(取出范围内的数据后再进行hash),如此大的两张表确定无法在50秒内(超时限制)完成,而输入了条件IOP.ORG_ID = :ORG_ID那么符合条件的INSX_OFFER_PRG_571的数据在100条之内,sql能够走嵌套循环,并且两张表均可以利用到高效的索引,速度天然就大大提升。
那么不带条件ORG_ID = :ORG_ID下能不能快呢?最后发现了一个关键点RP_BUSI_DETAIL_NNN是个按月分区表,每一个分区只有1-6G,比起180G天然是小的多了!并且分区字段也是DONE_DATE,很惋惜sql中没有利用到分区,当即联系需求侧和开发侧确认,INSX_OFFER_PRG_NNN 和RP_BUSI_DETAIL_NNN两张表的DONE_DATE在业务意义上是否一致或者差距很小?是否能够把RP_BUSI_DETAIL_NNN表的DONE_DATE也一并放在条件中?很是幸运,个人这个提议马上获得了确定的回复,在后续的沟通和测试下,从新定制了业务规则,最多只能按天然月查询(考虑表按天然月分区)查询,业务超时由原来的50秒改为3分钟,最终将SQL改写成了:
虽然目前的执行计划中INSX_OFFER_PRG_NNN 和RP_BUSI_DETAIL_NNN两张表仍是经过hash jion链接,数据量也仍是较大,可是咱们走上了分区过滤,特别是RP_BUSI_DETAIL_NNN这张大表只会单分区扫描,因此在新的规则和超时机制下咱们顺利的完成了这个报表的优化。
1.4 数据库迁移前的优化
背景:某个有点年头的数据库在某次项目改造中须要迁移并升级到新的机器上,这个库由于业务不停的上线和实在有点低的硬件配置缘由在迁移升级前主机CPU的使用率也是很高了,而迁移须要借助某第三方工具,这个工具的初始化过程须要有足够的CPU资源,而割接时间窗口和业务缘由只能准在线割接(能够停业务的时间很短),也就是说虽然这个数据库在目前的低配置机器上转不了几天了,可是为了保证割接的顺利进行DBA们仍是硬着头皮去进行优化。
优化过程当中的一个案例:
原sql:
这个SQL执行时间在25秒左右,逻辑上看起来实在是有点复杂,不过咱们耐心的拆解分析下找到了第一个突破点:修改如下子查询的写法
上面的写法等同于:
下面的写法执行时间能够从15秒降到10秒,有些进展!继续。
考虑到这个子查询所进行的表链接,能够将链接条件放置在这个子查询的where字句中,进一步简化逻辑,改写为:
该子查询的执行时间进一步减小到1.7秒,在原SQL中使用这个新子查询,原SQL的执行时间从25秒减小到4.6秒,性能提升了5倍多。好了,理解联系开发进行测试验证,2天后他们反馈业务效果彻底同样,并且速度的确明显快了不少!
看吧!咱们什么也没作,没有加索引,没有改造表的物理模型,仅仅经过梳理逻辑,简化sql执行的步骤和避免重复数据扫描就完成了一项优化,因此咱们之后在写代码的过程当中是否是要多想想呢?!经过2周的努力,咱们终于将主机CPU使用率给整体降下来接近10%,顺利的保障了割接。
1.关冲剑:绑定执行计划
1.1 合理利用Hints
Hints定制执行计划,在割接、经分、稽核等场景下应用普遍,合理的利用能够有效利用资源,提升执行效率。
Parallel 并行:合理的利用parallel能够有效利用资源,提升执行效率。可是在平常生产期间,核心库不容许吆!Parallel开的越高,就是并行度越高,那么资源使用就越严重,因此既要考虑效率,也要考虑负载,曾经在某些系统中抓到了/*+parallel(a,64)*/,这位老兄!咱们逻辑的CPU也不过只有32颗!
Select /*+parallel(a,6)*/count(*) from ins_offer_571 a where .....
Append nologging:Append nologging 高水位直接路径写入,减小写日志,经分,割接场景下较多使用
INSERT /*+ APPEND NOLOGGING PARALLEL(TMP_H_MD_PAR_EXT_USER_D16,3) */
INTO TMP_H_MD_PAR_EXT_USER_D16
(......)
SELECT /*+PARALLEL(R,3)*/*
from H_MD_BLL_TMN_BUSN_D_574 PARTITION(P20150406) R
/*+index(a,index_name)*/ 指定索引扫描
/*+full(a)*/ 指定全表扫描
........ 还有好多,这里不一一列举了,都在:select * from v$sql_hint
1.2 SQL PROFILE,还好有你!
某报表库有个前台的多选框操做的报表,sql的条件组合多样化,表RP_BUSI_DETAIL_NNN上存在12个索引,且多为组合索引,索引键重复状况较多,某天爆发了一下,大量的执行计划走错,统计信息收集了,游标刷出去了,没有效果。大白天的改造索引风险又过高,还好本sql有个特色,DONE_DATE字段必定要用的...... 状况紧急!DBA们还有一招 SQL PROFILE。
1 获取outline
select* fromtable(dbms_xplan.display_cursor('0bbt69m5yhf3p',null,'outline'));
2 建立sql profile 绑定执行计划
绑定完成后,kill执行计划有问题的SQL,数据库的性能就逐渐恢复了。后续业务梳理完成后改造了索引,各种sql的执行计划也就都稳定了。
2.少冲剑:怎么就忽然慢了?缘来是你!
某核心系统的维护找过来,每天跑的一个做业这几天变慢了,原来跑1.5-2个小时的做业,如今6个小时了也没跑出来。老板说搞定他!
Sql过来了:
执行计划先看看
这不是在动态采样嘛!仍是level=7的,难不成跑了6个小时仍是在动态采样中?开个10046看看会话在什么,一看不只仅有动态采样形成的问题呀,还有GC!
赶忙回访一下,果真这个做业一直就是跑在1号节点的。DR_GGPRS_XX_YYYYMMDD 是在2号节点入库的!那么在1号节点执行本做业就是有问题!
动态采样怎么办呢?这个DR_GGPRS_XX_YYYYMMDD表大地市超过400G,想收集统计信息是不可能的,况且做业天天仅仅跑一次,也不存在什么游标共享的问题了,干脆直接绑定执行计划,避免动态采样,在2号节点测试一下。
1590s完成!
通知应用,本做业调整到2号节点,并经过Hints /*+parallel(a,4) dynamic_sampling(a,0)*/
避免动态采样,后续反馈做业均可以在30分钟内完成。
3.少泽剑:高效的update,不能随心所欲!
某核心系统,Cpu使用率接近100%,监听的响应速度很是慢,主营业务数据入库积压严重!分析事后找到了罪魁祸首,居然是几个循环的update语句把CPU耗尽的!
咱们来看看他的威力:
仅仅从语句的变量中就发现了一个问题:本语句是循环执行的!并且循环次数很是多!找到应用方,电话回访,果真如此!并且本做业跑了一天了好没有跑完。
匹配条件表DR_GGPRS_XX_YYYYMMDD根据地市不一样在70-200G之间,数据量都是亿级,执行计划都不用看了,有没有索引都不重要了,就凭着这么循环下去,我也只能呵呵了!
被更新的表user_fenleijx_temp_YYYYMMDD很小啊,才几百MB!我前面说什么来着?!一个SQL能不能本质上被提速,看看他最终业务上须要扫描的数据就能够了,这必须能够提速啊!
咱们开始吧!
1.create tmp_table
create table jf.tmp_tab1 parallel 10 nologging as select distinct user_number from jf.dr_ggprs_jx_20150203 where charging_characteristics='04';
create table jf.tmp_tab2 parallel 10 nologging as select distinct user_number from jf.dr_ggprs_jx_20150203 where charging_characteristics='0400';
2.Create unique index
create unique index jf.ind_user_number_1 on jf.tmp_tab1(user_number) nologging;
create unique index jf.ind_user_number_2 on jf.tmp_tab2(user_number) nologging;
3.高效的update
update (select a.status
from bossmdy.user_fenleijx_temp_20150203 a,
jf.tmp_tab1 b
where a.user_number = b.user_number
and a.status = '3')
set status = '0';
update (select a.status
from bossmdy.user_fenleijx_temp_20150203 a,
jf.tmp_tab2 b
where a.user_number = b.user_number
and a.status = '3')
set status = '0';
不须要循环update,只须要建立一张匹配条件为惟一性的临时表,再建立一个惟一性索引,一次性update目标表,整个工做耗时10分钟。不只仅是高效,CPU资源的高消耗也没有了,数据库恢复了正常。
高效UPDATE方式
update (select a.col_a,b.col_b from t_tab_a a,t_tab_b b where a.id=b.id and b.col_x=’x’) c
set c.col_a=c.col_b;
要保证匹配条件b.id上有惟一性索引,若是没有惟一性索引则能够经过建立中间表的方式取出惟一性数据再建立惟一性索引如:
Create table t_tab_c as select * from t_tab_b where b.col_x=’x’;
Create unique index ind_t_tab_c on t_tab_c(id);
1.商阳剑:割接这三板斧!
提及割接,是让DBA、开发、测试等人都谈虎色变的事情,由于割接时间窗口紧张,割接资源消耗严重,割接要各类备份,割接产生大量日志,跑错了要回滚........总之风险有点高。
1.1 备份、临时表不写日志
割接中须要大量的备份、拍照,减小写日志能够有效提升效率,也能减轻数据库的归档压力,由于咱们不少库上还有DSG同步,减小日志量也能避免DSG分析延迟。
3.ddl 代替 dml,减轻undo压力
将A表的数据所有插入到新建的B表,推荐的作法:
create table B tablespace tbs_data nologging as select * from B;
rename 和 CTAS (create table as select)建表是在割接场景中很受欢迎的作法
1.2 批量提交,下降UNDO/REDO压力
割接的时候有不少场景须要对大表进行DML,一次性执行大表的DML通常效率较低,并且undo表空间压力很大,一旦取消,大事物回滚很是消耗性能,可能会影响后续割接,而用游标进行一条条的提交,一样会形成redo的IO问题,也可能形成大量的log file sync事件。所以对大表进行全量或者是近全量DML时咱们建议采用批量提交。
1.3 提升聚簇因子,只争朝夕!
在某个每个月一次的维护做业下有如上一段脚本,本质上就是对大表I_USER_STATUS_CENTER进行按条件的批量更新,表I_USER_STATUS_CENTER上有约16亿数据,且MSISDN列上有索引,distinct number约1.1万全部,也就是每次循环符合条件的数据:16亿/1.1万=15万。
都按照步骤作了,但是跑了2个小时也没有跑完........
执行计划也看过了,走索引,没问题!表的属性暂时改为nologging了,不写日志了!够了吧?!但是速度仍是不行,一分析,预计72小时以上.........在核心库上执行这样的做业哪里能够忍受?!该作的都作了,难道没办法再提速了么?
前面有个例子,操场上捡玻璃球,如今不是一个一个的捡,而是一组一组的捡,每组按照颜色不一样来捡,一次捡1.5万,若是这每组球都分散在不一样的地方,有了索引又如何呢,捡这么多还不是累死人?!若是说每组的球都堆在一个地方,不是分散到各个角落,那么是否是会快许多呢?!
这就是提升聚簇因子。
(1)将目标表按照MSISDN排序重建。
Alter table I_USER_STATUS_CENTER rename to I_USER_STATUS_CENTER_bak;
Create table I_USER_STATUS_CENTER tablespace tbs_data parallel 10 nologging as
Select /*+parallel(a,10)*/* from I_USER_STATUS_CENTER_bak a order by MSISDN;
Create index ind_ USER_STATUS_CENTER1 on I_USER_STATUS_CENTER(MSISDN) tablespace tbs_data parallel 10 nologging;
Alter table I_USER_STATUS_CENTER noparallel;
Alter index ind_ USER_STATUS_CENTER1 noparallel;
(2)MSISDN列上创建索引
(3)执行上面的脚本1.5小时完成
(4)修改表和索引的日志属性
Alter table I_USER_STATUS_CENTER logging;
Alter index ind_ USER_STATUS_CENTER1 logging;
(5)还能够更快么?
能够的!能够将游标按照业务字段net_id进行范围拆分,如:
SELECT NET_ID||HLR_SEGMENT BILL_ID,REGION_ID FROM RES_NUMBER_HLR where net_id >=1 and net_id<100;
SELECT NET_ID||HLR_SEGMENT BILL_ID,REGION_ID FROM RES_NUMBER_HLR where net_id >=100 and net_id<200;
........
拆分红8个游标,同时将这段程序放在不一样的窗口执行,那么15分钟以内咱们这个做业就会完成啦!
2.总结
本期的漫谈SQL优化就暂时讲到这里了,这里经过几个例子但愿能给你们一点启发。在SQL优化过程当中每每都不是那么顺利的,大部分状况下都是跟系统问题、业务需求、物理模型等紧密相连的,须要分析考虑的地方不少。兵无常势,水无常形。咱们这里也没有万能公式,不过咱们IT人有认真负责、灵活运用、钻研到底的态度,因此不少难题咱们最终都能迎刃而解!