order by 索引

一个容易被忽略的SQL调优技巧 --- order by字段到底要不要加入索引

字数 3507阅读 1275评论 1赞 2数据库

做者:邱峙微信

对于SQL调优,要调就调到极致,小编并非处女座,而是由于在一个并发量很大的业务系统中,对于频繁执行的单条SQL性能的提高,可能对总体数据库的性能提高都有很大的意义。
可是遇到order by字段后面的字段,特别是当这个字段不在过滤条件中时,小编就会内心打鼓,是加到索引里面呢,仍是不加到索引里面呢,加进去会不会没有起到提高性能的做用,反而让索引变得更加复杂,给系统带来没必要要的额外负担,“偷鸡不成蚀把米”,开个玩笑。可是若是直接忽略掉这个问题,极可能这个提高系统性能的机会就被错过了。
因此今天小编就和你们探讨一下,面对order by字段后面的条件,特别是这个条件不在过滤条件中时,到底要不要加入索引中,对于SQL调优这笔帐,索引中加入order by字段,是赚了仍是赔了❓并发

Part 1

空话很少说,先来一个小实验,热一下身。经过屡次复制dba_objects中的数据,生成测试表T1,大约1000万行数据。作一个简单的查询,查询T1表中object_id最小的10行数据,select * from (select * from T1 order by object_id) where rownum<=10,耗时‘Elapsed: 00:00:35.92’,执行计划以下:
微信图片_20170615135444.png
执行计划中能够看到,先做了一个全表扫,取到告终果集11M行(能够粗略理解为11百万行,这个测试表T1行数为11943842)。而后做了一个排序,截取最小的10条记录,最后返回结果。下面咱们在object_id字段上建一个索引I_T1_ORDER3,做一个比较。
耗时从刚才的35秒,直接降到了 ‘Elapsed: 00:00:00.01’,提高性能的效果很是明显。索引和执行计划以下:
微信图片_20170615135651.png
微信图片_20170615135738.png
从执行计划中能够看到,优化器直接从索引中找到了最小的10条记录,而后回表取得结果集返回。相比上一个执行计划,省去了全表扫描,省去了排序,因此执行时间和系统资源消耗都大大减小。
在这里做一个简单的分析,首先索引和数据不一样,是按照有序的排列存储的,当结果集要求按照顺序取得一部分数据时,索引的功效会体现的很是明显,本次查询就是要取得object_id最小的10条记录。其次,创建索引系统只须要消耗一次资源完成排序过程,而若是没有索引,执行不一样的语句可能每次都要经历排序的过程,会消耗更多的系统资源。从这个实验看,在order by字段建索引是很是划算的,并且order by字段并不必定非要加入到where条件中也能够生效。性能

这里小编要和你们分享一个本身踩到的“坑”,就是小编起初在建了索引I_T1_ORDER3后,这条查询语句的执行计划并不选择索引,增长了hint提示也不选择,小编都有点怀疑人生了,明显使用索引会好,为何优化器恰恰不选择索引呢,并且是加了hint也不走。在修改object_id列为非空属性(NOT NULL)后,优化器才选择了这个索引。小编这里是这么理解的,若是这一列存在NULL值,NULL值是没有大小这一说法的,并且不会被保存在索引中。若是优化器没法肯定该列没有NULL值,为了保证结果集的准确性,宁愿选择更慢的全表扫描,也不会选择走可能存在NULL的索引,即便用户指定了hint也不会选择(这里的几句话有点绕,你们耐心读一下)。从这一点来看,开发Oracle优化器的小伙伴是很是靠谱的。测试

Part 2

上面的实验中order by字段加入索引的做用很是明显。但是在实际生产环境中,能有这么简单的SQL来给DBA调优的机会并很少,实际生产中的SQL每每要更复杂一些。下面咱们就把测试变得复杂一点,复制测试表T1,生成测试表T2,查询object_type相似INDEX中object_id最小的10条记录,select * from (select * from T2 where object_type like '%INDEX%' order by object_id) where rownum<=10。
这条语句比第一个实验中多了过滤条件,可是使用了like方法。按一般的经验建索引首先会考虑where条件后的字段,可是在使用like的过滤条件上创建索引,效果可能并很差。但是若是这条语句是业务系统中执行频率很是高的语句呢,咱们仍是硬着头皮优化一下吧。先看一下没有索引的状况。
微信图片_20170615135945.png
执行时间“Elapsed: 00:00:08.75”,接近9s,从执行计划中看到,先是全表扫描过滤出了1597K条(1597K约163万条)记录,而后做了个排序,返回object_id最小的10条记录。
这样的执行效率在生产系统中是不能接受的,可是在like列上建索引效果可能并很差,本着敬业的精神,仍是试一下吧。在仅有的两个条件 object_type和object_id上建一个复合索引I_T2_ORDER2,并
加入hint提示,结果以下:
微信图片_20170615140027.png
微信图片_20170615140030.png
执行时间“Elapsed: 00:00:17.25”,比刚才9秒还多花了8秒。从执行计划中能够看到,先是在索引I_T2_ORDER2中定位到1597K条记录,而后回表取得1597K记录的结果集,再排序取到object_id最小的10条记录。与上一个执行计划相比,反而增长了一个读索引的步骤,因此系统资源消耗更多,执行时间也更长,并且虽然order by字段加入到索引中,并无省去排序的步骤。在这里这个索引建的就有点亏了。
“理想很丰满,现实很骨感”,看来SQL变得复杂之后,order by字段在索引里面果真不灵了,这招很差使。不要着急,我们分析一下,为何很差使了。你们都知道索引是树状结构,如今I_T2_ORDER2索引中有两个字段,这个索引结构大概是这个样子的,以下图。
微信图片_20170615140106.png
你们能够看到,对应INDEX节点下面的object_id“3,9,13”是有序的, INDEX PARTITION节点也相似。可是把INDEX节点和INDEX PARTITION节点对应的object_id放到一块儿,“3,9,13…2,15,17”,就变得无序了,因此优化器虽然使用了索引,但不得再也不作一遍排序,order by索引的功效并无发挥出来。
看到这里是否是有点灰心了,这条语句无法优化了。看下本文的标题,换个角度想一下,说不定这条语句还有救。与测试表T1同样,在object_id上建一个索引I_T2_ORDER3试一下。
微信图片_20170615140141.png
微信图片_20170615140144.png
执行时间从17s,直接变为“Elapsed: 00:00:00.01”,从执行计划能够看到,优化器经过索引过滤了817条记录后获得了想要的10条结果,以后回表取得结果返回。与上面的执行计划相比,时间消耗和资源消耗都大大减小。
这里咱们简单分析一下,索引I_T2_ORDER3是按照object_id有序排列的,当优化器按序处理到817条记录时,就已经获得了想要的object_type相似INDEX,object_id最小的10条记录,而后回表取到结果并返回,省去了全表扫描以及排序的消耗,因此效率大大提高。索引结构以下图。
微信图片_20170615140227.png
执行时间和系统消耗,都大大减小,那么到这里咱们是否是能够交差了。再看一下咱们文章的开头,“对于SQL调优,要调就调到极致”, “对于频繁执行的单条SQL性能的提高,对总体数据库的性能提高都有很大的意义”。咱们再想一下还可不能够更优。小编在这里又建了一个索引I_T2_ORDER4,再执行这条查询语句。
微信图片_20170615140358.png
微信图片_20170615140403.png
执行时间“Elapsed: 00:00:00.01”,从执行计划中能够看到,优化器经过索引直接定位到了想要的10条记录,回表取得10条记录并返回。最终结果只有10条记录,优化器也只处理了10条记录,几乎没有任何的资源浪费。I_T2_ORDER4索引的结构图以下,能够看到,过滤条件已经在索引中存储了,因此优化器能够在索引中直接定位到最终的10条记录。
微信图片_20170615140458.png
到这里,从建索引的角度出发,小编认为这条SQL的优化能够交差了。优化

Part 3

最后小编想说的是,遇到相似order by字段是否加入索引的问题,或者其余一些你们犹豫的问题,能够大胆的尝试,并打开思路,从不一样的角度考虑,多作测试,不要错过任何一个提高性能的机会。
对于order by字段加入索引自己这个问题,若是最终的结果集是以order by字段为条件筛选的,将order by字段加入索引,并放在索引中正确的位置,会有明显的性能提高。不过这里要注意小编前面提到的那个坑,order by字段须要是非空的属性,不然会无效。
好了,今天的分享就到这里,你们能够关注咱们的专栏。spa

相关文章
相关标签/搜索