建立理想的数据库索引

简介

    当程序中全部的SQL都是用到了一个或者多个索引,许多DBA就会对此感到满意,认为一切都看起来正常。可是,使用一个不合适的索引有可能会致使比全表扫面更差的性能。本随笔将详细地考虑这些极其重要的问题。首先给出咱们讨论问题所需的前提假设。程序员

磁盘及CPU时间的基础假设

    磁盘随机读取一次4k或者8k大小的页,须要10ms,顺序读取速度40MB/s;CPU扫描一行记录须要5us,从缓存中获取一次数据须要100us;算法

不合适的索引

    对于下面的简单SQL查询,仅有的两个合理的访问路径:数据库

    一、索引扫描缓存

    二、全表扫描性能

    即便对于最广泛的姓氏(过滤因子1%),这两种选择是否可以提供可接受的响应时间呢?优化

    对于第一种选择,数据库管理系统会根据where条件LNAME=:LNAME扫描索引片。对于索引片中的每个索引行,数据库管理系统都必须回到表里检查CITY的值。因为表中的行是根据CNO而不是LNAME聚簇的,因此这个检查操做须要一次磁盘的随机读取。对于最广泛的姓氏,在不考虑CITY的过滤因子的条件下,获取完整的结果集意味着,需比对10000个索引行和10000个表行。那么,这个过程会耗时多少?设计

    假设索引(LNAME,FNAME)的大小是10000*100byte=100MB,包括数据和离散的空闲空间,另外再假设顺序读的速度是40MB/s。读取一个宽度为1%的索引片,即1MB,需花费10ms+1/40s=35ms,这显然没有问题,可是10000次随机表读取需花费10000*10ms=100s,这使得这种方式太慢了。3d

    对于第二种选择,只有第一个页须要随机读。若是表的大小为1000000*600byte=600MB,包括数据即分散的空闲空间,那么花费的I/O时间为10ms+600/40s=15s,仍旧很慢。blog

    第二种选择的CPU时间将会比第一种选择的时间长得多,由于数据库系统须要比对1000000行而不是20000行,并且还要对这些行进行排序。从另外一个方面看,因为是顺序读取,cpu时间跟I/O时间交叠。在这个场景下,全表扫描要比在不合适的索引上扫描快,但这还不够快,须要有一个更好的索引。排序

三星索引---查询语句的理想索引

    前一小节咱们讨论了CURSOR41的不合适的索引,这一小结咱们讨论另外一个极端,三星索引,即对于一个查询语句可能的最理想索引。相似图4.2中的查询语句,若是使用了三星索引,只需一次随机读取和一次窄索引片的扫描。所以,其响应时间会比使用普通索引的响应时间少几个数量级。

    即便返回的结果集有1000行,CURSOR41(见SQL4.2)的响应时间也不足1s,这是怎么作到的呢?图4.2展现了索引最低一层叶子页的状况。

    若是结果集只有1000行的话,那么组合where条件LNAME =: LNAME AND CITY =:CITY的过滤因子就是0.1%。被扫描的索引片就只有1000行,由于索引片的宽度彻底由LNAME和CITY两个条件所决定。这种状况下,查询将花费1*1ms + 1000*0.1ms=0.1s。在这个过程当中,表根本就没有被访问过,由于所需的列值都被复制到了索引中了。 

 星级是如何给定的

    若是与一个查询相关的索引行是相邻的,或者至少相距足够靠近的话,那么这个索引就能够被标上第一颗星。这最小化了必须扫描的索引片的宽度。

    若是索引行的顺序跟查询语句的需求一致,则索引能够被标记上第二颗星。这排除了排序操做。

    若是索引行包含查询语句中的全部列,那么索引能够标记上第三颗星。这能够避免对表的操做:由于直接访问索引就能够了。

    对于这三颗星,第三颗星一般是最重要的,将一个列排除在索引以外可能会致使许多速度较慢的磁盘随机读。咱们把至少包含第三颗星的索引称做对应查询语句的宽索引。

 

为了知足第一颗星 

     取出全部等值条件(等值,并非范围)的列。把这些列做为索引最开头的列,以任意顺序均可以。对于CURSOR4.1来讲,三星索引能够以LNAME,CITY或CITY,LNAME开头。在这两种状况下,必须扫描的索引片宽度将索至最小。

为了知足第二颗星

    将order by列加入到索引中。不要改变这些列的顺序,可是忽略那些在第一步中已经加入索引的列。例如,若是在CURSOR4.1 order by中有重复的列,好比 order by FNAME,LNAME或者order by CITY,FNAME,只有FNAME列须要被加到索引中。当FNAME是索引的第三列时,结果集中的记录无需排序就已是以正确的顺序排序的了。第一次读取操做将返回FNAME值最小的那一行。

为了知足第三颗星

    将查询语句中剩余的列加到索引的尾部,列在索引中添加的顺序对查询语句的性能没有影响,可是将易变的列放在最后能下降更新的成本。如今,索引已包含了知足无须回表的访问路径所须要的所有列。

    最终三星索引将会是:

    (LNAME,CITY,FNAME,CNO)或(CITY,FNAME,CNO,LNAME)

     CURSOR4.1在如下三个方面是最为挑剔的:

  • where条件不包含访问条件(between,>,<等);
  • from语句指设计单表
  • 全部条件对优化器来讲都足够简单

范围条件和三星索引

下面的SQL4.3须要的信息跟以前相同,只是如今LNAME是在一个范围内。

 让咱们为这个CURSOR设计一个三星索引。大部分的推论跟CURSOR4.1相同,可是“between条件”将“=条件”替换后将会有很大的影响。咱们将以相反的顺序考虑三颗星。

    首先是第三颗星,按照先前所述,确保查询语句中的全部列都在索引中就能知足第三颗星。这样不须要访问表,那么同步读也就不会形成问题。

    添加order by列能使索引知足第二颗星,可是这个仅在将其放在between范围条件列LNAME以前的状况下才成立,如索引(CITY,FNAME,LNAME),因为CITY的值只有一个,因此使用这个索引可使结果集以FNAME的顺序排列,而不须要额外的排序。可是若是order by字段加在between范围条件列LNAME后面,如索引(CITY,LNAME,FNAME),那么索引行不是按照FNAME排序的,须要对结果集进行额外的排序。所以为了知足第二颗星,FNAME必须放在范围条件列LNAME以前,如索引(FNAME,...)或者(CITY,FNAME,...)。

    再考虑第一颗星,若是CITY放在索引的第一列,那咱们将会有一个相对较窄的索引片须要扫描,这取决于CITY的过滤因子。可是若是使用(CITY,LNAME)的话,索引片将会更窄,这样在有两个列的状况下咱们只须要访问真正须要的索引列。可是,为了作到这样,并从一个很窄的索引片中获益,其余列(如FNAME)就不能放在这两列之间。

    因此,咱们的理想索引会有几颗星呢?首先,确定能有第三颗星,可是,正如咱们刚才所说,咱们只能有第一颗星或者第二颗星,而不能同时拥有二者!换句话说,咱们只能二选一:

  • 避免排序,选择第二颗星,或者
  • 拥有可能的最窄索引片,不只将须要处理的索引行数降至最低,并且将后续处理量,特别是表数据的同步读,减小到最低,拥有第一颗星。   

     在这个例子中,因为between或者其余任何访问条件的出现,意味着咱们不能同时拥有第一和第二颗星。也就是说咱们不能拥有一个三星索引。

为查询语句设计最佳索引的算法

    根据以上的讨论,理想的索引是一个三星索引。然而,正如咱们所见,当存在访问条件时,这是不可能实现的。咱们(也许)不得不牺牲第二颗星来知足一个更窄的索引片,这样最佳索引就只拥有两颗星。这也就是为何咱们要仔细区分理想和最佳。在这个例子中,理想索引是不可能实现的。将这层因素考虑在内,咱们能够对全部状况下建立最佳索引的过程公式化。建立出的索引将拥有三颗星或者两颗星。

    首先设计一个索引片尽量窄(第一颗星)的宽索引(第三颗星)。若是查询使用时不须要排序,那这个索引就是三星索引。不然,这个索引就只能是二星索引,牺牲第二颗星。或者采起另外一种选择,避免排序,牺牲第一颗星保留第二颗星。

   下面咱们阐述为查询语句建立最佳索引的算法。

候选A

  1. 取出对于优化器来讲不过度复杂的等值条件列。将这些列做为索引的前导列,以任意顺序皆可。
  2. 将选择性最好的范围条件做为索引的下一个列,若是存在的话。最好的选择性是指对于最差的输入有最低的过滤因子。只考虑对于优化器来讲不过度复杂的范围条件便可。
  3. 以正确的顺序添加order by列,(若是列有DESC的话,加上DESC)。忽略在第一步或第二步已经添加的列。
  4. 以任意顺序将select语句中其他的列添加至索引中(可是须要以不易变的列开始)。

候选B

    若是候选A引发了所给查询语句的一次排序操做,那么还能够设计候选B。根据定义,对于候选B来讲,第二颗星比第一颗星更重要。

  1. 取出对于优化器来讲不过度复杂的等值条件列。将这些列做为索引的前导列,以任意顺序皆可。
  2. 以正确的顺序添加order by列,(若是列有DESC的话,加上DESC)。忽略在第一步中已经添加的列。
  3. 以任意顺序将select语句中其他的列添加至索引中(可是须要以不易变的列开始)。

现今排序速度那么快,为何还须要候选B

    近几年来,排序速度已经提高了不少。如今大多数的排序过程都在内存中进行,用当下最快的处理器排序一行花费的CPU时间大约在5us左右。所以,排序50000行的数据所耗费的时间只有0.5s。这对于一次事务操做来讲是能够接受的,但对于CPU时间来讲是一个比较大的开销。

    因为在如今的硬件条件下排序速度很快,因此若是一个程序取出结果集的全部行,那么候选A可能和候选B同样快,甚至比候选B更快。对于程序员来讲,这是最方便的解决方案。许多环境都提供了灵活的命令来浏览结果集。

    然而,若是一个程序只需获取可以填充满一个屏幕的数据量,那么候选B可能会比候选A快得多。若是结果集很大的话,为了产生第一屏的数据,二星索引候选A(须要排序)可能会花费很是长的时间。咱们须要时刻记着,客户端的一次错误输入可能会使得结果集变得很是大。

    若是访问路径中没有排序的话,使用CURSOR44程序将会很是快(假设LNAME和CITY是索引的前两列,无论顺序如何),即便结果集包含数以百万级的数据行。每一个事务永远都不会使数据库管理系统物化大于20行的数据。

相关文章
相关标签/搜索