SQL索引优化

序言
数据库的优化方法有不少种,在应用层来讲,主要是基于索引的优化。本次秘笈根据实际的工做经验,在研发原来已有的方法的基础上,进行了一些扩充,总结了基于索引的SQL语句优化的降龙十八掌,但愿有一天你能用其中一掌来驯服客服业务中横行的‘恶龙’
总纲
创建必要的索引
此次传授的降龙十八掌,总纲只有一句话:创建必要的索引,这就是后面降龙十八掌的内功基础。这一点看似容易实际却很难。难就难在如何判断哪些索引是必要的,哪些又是没必要要的。判断的最终标准是看这些索引是否对咱们的数据库性能有所帮助。具体到方法上,就必须熟悉数据库应用程序中的全部SQL语句,从中统计出经常使用的可能对性能有影响的部分SQL,分析、概括出做为Where条件子句的字段及其组合方式;在这一基础上能够初步判断出哪些表的哪些字段应该创建索引。其次,必须熟悉应用程序。必须了解哪些表是数据操做频繁的表;哪些表常常与其余表进行链接;哪些表中的数据量可能很大;对于数据量大的表,其中各个字段的数据分布状况如何;等等。对于知足以上条件的这些表,必须重点关注,由于在这些表上的索引,将对SQL语句的性能产生举足轻重的影响。不过下面仍是总结了一降低龙十八掌内功的入门基础,创建索引经常使用的规则以下:前端

 

一、表的主键、外键必须有索引;数据库

二、数据量超过300的表应该有索引;session

三、常常与其余表进行链接的表,在链接字段上应该创建索引;数据库设计

四、常常出如今Where子句中的字段,特别是大表的字段,应该创建索引;函数

五、索引应该建在选择性高的字段上;性能

六、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;优化

七、复合索引的创建须要进行仔细分析;尽可能考虑用单字段索引代替:设计

A、正确选择复合索引中的主列字段,通常是选择性较好的字段;orm

B、复合索引的几个字段是否常常同时以AND方式出如今Where子句中?单字段查询是否极少甚至没有?若是是,则能够创建复合索引;不然考虑单字段索引;排序

C、若是复合索引中包含的字段常常单独出如今Where子句中,则分解为多个单字段索引;

D、若是复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减小复合的字段;

E、若是既有单字段索引,又有这几个字段上的复合索引,通常能够删除复合索引;

八、频繁进行数据操做的表,不要创建太多的索引;

九、删除无用的索引,避免对执行计划形成负面影响;

以上是一些广泛的创建索引时的判断依据。一言以蔽之,索引的创建必须慎重,对每一个索引的必要性都应该通过仔细分析,要有创建的依据。由于太多的索引与不充分、不正确的索引对性能都毫无益处:在表上创建的每一个索引都会增长存储开销,索引对于插入、删除、更新操做也会增长处理上的开销。另外,过多的复合索引,在有单字段索引的状况下,通常都是没有存在价值的;相反,还会下降数据增长删除时的性能,特别是对频繁更新的表来讲,负面影响更大。

第一掌 避免对列的操做


任何对列的操做均可能致使全表扫描,这里所谓的操做包括数据库函数、计算表达式等等,查询时要尽量将操做移至等式的右边,甚至去掉函数。


例1:下列SQL条件语句中的列都建有恰当的索引,但30万行数据状况下执行速度却很是慢:


select * from record where substrb(CardNo,1,4)='5378'(13秒)


select * from record where amount/30< 1000(11秒)


select * from record where to_char(ActionTime,'yyyymmdd')='19991201'(10秒)


因为where子句中对列的任何操做结果都是在SQL运行时逐行计算获得的,所以它不得不进行表扫描,而没有使用该列上面的索引;若是这些结果在查询编译时就能获得,那么就能够被SQL优化器优化,使用索引,避免表扫描,所以将SQL重写以下:


select * from record where CardNo like '5378%'(< 1秒)


select * from record where amount < 1000*30(< 1秒)


select * from record where ActionTime= to_date ('19991201' ,'yyyymmdd')(< 1秒)


差异是很明显的!


第二掌 避免没必要要的类型转换


须要注意的是,尽可能避免潜在的数据类型转换。如将字符型数据与数值型数据比较,ORACLE会自动将字符型用to_number()函数进行转换,从而致使全表扫描。


例2:表tab1中的列col1是字符型(char),则如下语句存在类型转换:


select col1,col2 from tab1 where col1>10,


应该写为: select col1,col2 from tab1 where col1>'10'。


第三掌 增长查询的范围限制


增长查询的范围限制,避免全范围的搜索。


例3:如下查询表record 中时间ActionTime小于2001年3月1日的数据:


select * from record where ActionTime < to_date ('20010301' ,'yyyymm')


查询计划代表,上面的查询对表进行全表扫描,若是咱们知道表中的最先的数据为2001年1月1日,那么,能够增长一个最小时间,使查询在一个完整的范围以内。修改以下: select * from record where


ActionTime < to_date ('20010301' ,'yyyymm')


and ActionTime > to_date ('20010101' ,'yyyymm')


后一种SQL语句将利用上ActionTime字段上的索引,从而提升查询效率。把'20010301'换成一个变量,根据取值的机率,能够有一半以上的 机会提升效率。同理,对于大于某个值的查询,若是知道当前可能的最大值,也能够在Where子句中加上 “AND 列名<MAX(最大值)”。


第四掌 尽可能去掉"IN"、"OR"


含有"IN"、"OR"的Where子句常会使用工做表,使索引失效;若是不产生大量重复值,能够考虑把子句拆开;拆开的子句中应该包含索引。


例4:select count(*) from stuff where id_no in('0','1')(23秒)


能够考虑将or子句分开:


select count(*) from stuff where id_no='0'


select count(*) from stuff where id_no='1'


而后再作一个简单的加法,与原来的SQL语句相比,查询速度更快。

第五掌 尽可能去掉 "<>"

尽可能去掉 "<>",避免全表扫描,若是数据是枚举值,且取值范围固定,则修改成"OR"方式。

例5:

UPDATE SERVICEINFO SET STATE=0 WHERE STATE<>0;

以 上语句因为其中包含了"<>",执行计划中用了全表扫描(TABLE ACCESSFULL),没有用到state字段上的索引。实际应用中,因为业务逻辑的限制,字段state为枚举值,只能等于0,1或2,并且,值等于=1,2的不多,所以能够去掉"<>",利用索引来提升效率。

修改成:UPDATE SERVICEINFO SET STATE=0 WHERE STATE = 1 OR STATE = 2 。进一步的修改能够参考第4种方法。

第六掌 去掉Where子句中的IS NULL和IS NOT NULL

Where字句中的IS NULL和IS NOT NULL将不会使用索引而是进行全表搜索,所以须要经过改变查询方式,分状况讨论等方法,去掉Where子句中的IS NULL和IS NOT NULL。

第七掌 索引提升数据分布不均匀时查询效率

索引的选择性低,但数据的值分布差别很大时,仍然能够利用索引提升效率。A、数据分布不均匀的特殊状况下,选择性不高的索引也要建立。

表 ServiceInfo中数据量很大,假设有一百万行,其中有一个字段DisposalCourseFlag,取值范围为枚举值:[0,1,2,3,4,5,6,7]。按照前面说的索引创建的规则,“选择性不高的字段不该该创建索引,该字段只有8种取值,索引值的重复率很高,索引选择性明显很低,所以不建索引。然而,因为该字段上数据值的分布状况很是特殊,具体以下表:


取值范围 1~5 6 7

占总数据量的百分比 1% 98% 1%


并且,经常使用的查询中,查询DisposalCourseFlag<6 的状况既多又频繁,毫无疑问,若是可以创建索引,而且被应用,那么将大大提升这种状况的查询效率。所以,咱们须要在该字段上创建索引。

第八掌 利用HINT强制指定索引

在ORACLE优化器没法用上合理索引的状况下,利用HINT强制指定索引。

继续上面7的例子,ORACLE缺省认定,表中列的值是在全部数据行中均匀分布的,也就是说,在一百万数据量下,每种DisposalCourseFlag值各有12.5万数据行与之对应。假设SQL搜索条件DisposalCourseFlag=2,利用DisposalCourseFlag列上的索引进行数据搜索效率,每每不比全表扫描的高,ORACLE所以对索引“视而不见”,从而在查询路径的选择中,用其余字段上的索引甚至全表扫描。根据咱们上面的分析,数据值的分布很特殊,严重的不均匀。为了利用索引提升效率,此时,一方面能够单独对该字段或该表用analyze语句进行分析,对该列搜集足够的统计数据,使ORACLE在查询选择性较高的值时能用上索引;另外一方面,能够利用HINT提示,在SELECT关键字后面,加上“/*+INDEX(表名称,索引名称)*/”的方式,强制ORACLE优化器用上该索引。

好比: select * from serviceinfo where DisposalCourseFlag=1 ;

上面的语句,实际执行中ORACLE用了全表扫描,加上蓝色提示部分后,用到索引查询。以下:

select /*+ INDEX(SERVICEINFO,IX_S_DISPOSALCOURSEFLAG) */ *

from serviceinfo where DisposalCourseFlag=1;

请注意,这种方法会加大代码维护的难度,并且该字段上索引的名称被改变以后,必需要同步全部指定索引的HINT代码,不然HINT提示将被ORACLE忽略掉。

第九掌 屏蔽无用索引

继续上面8的例子,因为实际查询中,还有涉及到DisposalCourseFlag=6的查询,而此时若是用上该字段上的索引,将是很是不明智的,效率也极低。所以这种状况下,咱们须要用特殊的方法屏蔽该索引,以便ORACLE选择其余字段上的索引。好比,若是字段为数值型的就在表达式的字段名后,添加“+ 0”,为字符型的就并上空串:“||""”

如: select * from serviceinfo where DisposalCourseFlag+ 0 = 6 and workNo = '36' 。

不过,不要把该用的索引屏蔽掉了,不然一样会产生低效率的全表扫描。
第十掌 分解复杂查询,用常量代替变量

对于复杂的Where条件组合,Where中含有多个带索引的字段,考虑用IF语句分状况进行讨论;同时,去掉没必要要的外来参数条件,减低复杂度,以便在不一样状况下用不一样字段上的索引。

继续上面9的例子,对于包含

Where (DisposalCourseFlag < v_DisPosalCourseFlag) or(v_DisPosalCourseFlag is null) and....的查询,(这里v_DisPosalCourseFlag为一个输入变量,取值范围可能为[NULL,0,1,2,3,4,5,6,7]),能够考虑分状况用IF语句进行讨论,相似:

IF v_DisPosalCourseFlag =1 THEN

Where DisposalCourseFlag = 1 and ....

ELSIF v_DisPosalCourseFlag =2 THEN

Where DisposalCourseFlag = 2 and ....

。。。。。。

第十一掌 like子句尽可能前端匹配

由于like参数使用的很是频繁,所以若是可以对like子句使用索引,将很高的提升查询的效率。

例6:select * from city where name like ‘%S%’

以上查询的执行计划用了全表扫描(TABLE ACCESS FULL),若是可以修改成:

select * from city where name like ‘S%’

那 么查询的执行计划将会变成(INDEX RANGE SCAN),成功的利用了name字段的索引。这意味着OracleSQL优化器会识别出用于索引的like子句,只要该查询的匹配端是具体值。所以咱们在作like查询时,应该尽可能使查询的匹配端是具体值,即便用like ‘S%’。

第十二掌 用Case语句合并多重扫描

咱们经常必须基于多组数据表计算不一样的汇集。例以下例经过三个独立查询:

例8:1)select count(*) from emp where sal<1000;

2)select count(*) from emp where sal between 1000 and 5000;

3)select count(*) from emp where sal>5000;

这样咱们须要进行三次全表查询,可是若是咱们使用case语句:

select

count (sale when sal <1000 then 1 else null end) count_poor,

count (sale when between 1000 and 5000 then 1 else null end) count_blue,

count (sale when sal >5000 then 1 else null end) count_poor

from emp;

这样查询的结果同样,可是执行计划只进行了一次全表查询。

第十三掌 使用nls_date_format

例9:

select * from record where to_char(ActionTime,'mm')='12'

这个查询的执行计划将是全表查询,若是咱们改变nls_date_format,

SQL>alert session set nls_date_formate=’MM’;

如今从新修改上面的查询:

select * from record where ActionTime='12'

这样就能使用actiontime上的索引了,它的执行计划将是(INDEX RANGE SCAN)。
第十四掌 使用基于函数的索引

前面谈到任何对列的操做均可能致使全表扫描,例如:

select * from emp where substr(ename,1,2)=’SM’;

可是这种查询在客服系统又常用,咱们能够建立一个带有substr函数的基于函数的索引,

create index emp_ename_substr on eemp ( substr(ename,1,2) );

这样在执行上面的查询语句时,这个基于函数的索引将排上用场,执行计划将是(INDEX RANGE SCAN)。

第十五掌 基于函数的索引要求等式匹配

上面的例子中,咱们建立了基于函数的索引,可是若是执行下面的查询:

select * from emp where substr(ename,1,1)=’S’

得 到的执行计划将仍是(TABLE ACCESSFULL),由于只有当数据列可以等式匹配时,基于函数的索引才能生效,这样对于这种索引的计划和维护的要求都很高。请注意,向表中添加索引是很是危险的操做,由于这将致使许多查询执行计划的变动。然而,若是咱们使用基于函数的索引就不会产生这样的问题,由于Oracle只有在查询使用了匹配的内置函数时才会使用这种类型的索引。

第十六掌 使用分区索引

在用分析命令对分区索引进行分析时,每个分区的数据值的范围信息会放入Oracle的数据字典中。Oracle能够利用这个信息来提取出那些只与SQL查询相关的数据分区。

例如,假设你已经定义了一个分区索引,而且某个SQL语句须要在一个索引分区中进行一次索引扫描。Oracle会仅仅访问这个索引分区,并且会在这个分区上调用一个此索引范围的快速全扫描。由于不须要访问整个索引,因此提升了查询的速度。

第十七掌 使用位图索引

位图索引能够从本质上提升使用了小于1000个惟一数据值的数据列的查询速度,由于在位图索引中进行的检索是在RAM中完成的,并且也老是比传统的B树索引的速度要快。对于那些少于1000个惟一数据值的数据列创建位图索引,可使执行效率更快。

第十八掌 决定使用全表扫描仍是使用索引

和全部的秘笈同样,最后一招都会又回到起点,最后咱们来讨论一下是否须要创建索引,也许进行全表扫描更快。在大多数状况下,全表扫描可能会致使更多的物理磁盘输入输出,可是全表扫描有时又可能会由于高度并行化的存在而执行的更快。若是查询的表彻底没有顺序,那么一个要返回记录数小于10%的查询可能会读取表中大部分的数据块,这样使用索引会使查询效率提升不少。可是若是表很是有顺序,那么若是查询的记录数大于40%时,可能使用全表扫描更快。所以,有一个索引范围扫描的整体原则是:

1)对于原始排序的表 仅读取少于表记录数40%的查询应该使用索引范围扫描。反之,读取记录数目多于表记录数的40%的查询应该使用全表扫描。

2)对于未排序的表 仅读取少于表记录数7%的查询应该使用索引范围扫描。反之,读取记录数目多于表记录数的7%的查询应该使用全表扫描。

总结

以上的招式,是彻底能够相互结合同时运用的。并且各类方法之间相互影响,紧密联系。这种联系既存在一致性,也可能带来冲突,当冲突发生时,须要根据实际状况进行选择,没有固定的模式。最后决定SQL优化功力的因素就是对ORACLE内功的掌握程度了。

另外,值得注意的是:随着时间的推移和数据的累计与变化,ORACLE对SQL语句的执行计划也会改变,好比:基于代价的优化方法,随着数据量的增大,优化器可能错误的不选择索引而采用全表扫描。这种状况多是由于统计信息已通过时,在数据量变化很大后没有及时分析表;但若是对表进行分析以后,仍然没有用上合理的索引,那么就有必要对SQL语句用HINT提示,强制用合理的索引。但这种HINT提示也不能滥用,由于这种方法过于复杂,缺少通用性和应变能力,同时也增长了维护上的代价;相对来讲,基于函数右移、去掉“IN ,OR ,<> ,IS NOT NULL”、分解复杂的SQL语句等等方法,倒是“放之四海皆准”的,能够放心大胆的使用。

同时,优化也不是“一劳永逸”的,必须随着状况的改变进行相应的调整。当数据库设计发生变化,包括更改表结构:字段和索引的增长、删除或更名等;业务逻辑发生变化:如查询方式、取值范围发生改变等等。在这种状况下,也必须对原有的优化进行调整,以适应效率上的需求。