首页 > 代码库 > MySQL 索引知识整理(创建高性能的索引)

MySQL 索引知识整理(创建高性能的索引)

前言:

     索引优化应该是对查询性能优化的最有效的手段了。索引能够轻易将查询性能提高几个数量级。

 

     // 固态硬盘驱动器有和机械硬盘启动器,有着完全不同的性能特性;

         然而即使是固态硬盘,索引的原则依然成立,

         只是那些需要尽量避免的糟糕索引对固态硬盘的影响没有机械硬盘那么糟糕。

 

     现在很多公司都将数据库的优化工作都依托于 DBA 去完成,在我看来,这些都应该是程序员必备的技能,

     有经验和没经验的程序员在数据库使用起来也有很大的差异,这些差异取决开发人员对索引内部的数据结构认识,

     对所有负责的业务熟悉程度,从而才能建立卓越的索引,达到性能最大化。

 

一、索引基础

     

     索引在数据库中的作用,粗暴的原理介绍不做解释,太基础。

     在 MySQL 中,索引可以包含一个或者多个列的值。 

     如果索引包含多个列,那么列的顺序十分重要,因为 MySQL 只能高效地使用索引最前缀列。

     创建一个包含两个列的索引,和创建两个只包含一个列的索引大有不同。

 

二、索引的类型

     

     MySQL 中有两种索引类型:BTree 和 Hash;

     不同的存储引擎的索引工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。

     即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。

     MyISAM 使用前缀压缩技术使得索引更小,但 InnoDB 则按照原数据格式进行储存。

     MyISAM 索引通过数据的物理位置引用被索引的行,而 InnoDB 则根据主键引用被索引的行。

 

     B-Tree 索引

          // 实际上很多存储引擎所用的都是 B+Tree,即每一个叶子节点都包含指向下一个叶子节点的指针,

             从而方便叶子节点的范围遍历。(关于 B-Tree 更详细的细节参考数据结构相关书籍)

 技术分享

 

     B-Tree 索引能够加快访问数据的速度,因为存储引擎不再需要进行权标扫描来获取需要的数据,

     取而代之是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。

     通过比较节点页的值和要查找的值可以找到合适的指针进入下一层子节点。这些指针实际上定义了子节点页中值的上限和下限。

     最终存储引擎要么是找到对应的值,要么该记录不存在。

 技术分享

 

     B-Tree 通常意味着所有的值都是按顺序储存的,并且每一个叶子页到根的距离相同。

     B-Tree 对索引是顺序组织存储的,所以很适合查找范围数据。

     B-Tree 索引适用于全键值、键值范围或键前缀查找。     

 

     全值匹配:匹配(检索)索引文件中所有的列;

     匹配最左前缀:假设查找 Name="Allen" 的人,只检索索引的第一列;

     匹配列前缀:匹配值的开头部分,假设查找 Name LIKE ‘A%‘ 也只检索索引的第一列;

     匹配范围值:假设匹配 Allen ~ Bell 之间的人,也只检索索引的第一列;

 

     索引的限制:

     1、如果不是按照索引的最左列开始查找,则无法使用到索引。(字符串类型)

     2、不能跳索引中的列。      // 没理解可能说的组合索引的情况。(注解 ①)

     3、如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。

 注解 ①

 技术分享

 

     哈希索引

     // 目前只有 Memory 引擎支持哈希索引,所以不常用;

     哈希索引(Hash Index)基于哈希表(键值对)实现,只有精准匹配索引所有列的查询才有效;

     对每一行数据,存储引擎都会对所有的索引列计算一个哈希码(Key),哈希码是一个较小的值;

 

     哈希索引的限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。

     哈希索引无法用于排序。 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容计算哈希值的。

     例如在数据列 (A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。

     哈希索引只支持等值比较查询,包括 =, IN(), <=>(注意<>和<=>是不同的操作)。也不支持任何范围查询。

     访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列却有相同的哈希值)。

     如果哈希冲突很多的话,一些索引维护操作的代价也会很高。 因为这些限制,哈希索引只适用于某些特定的场合。

     而一旦适合哈希索引,则它带来的性能提升将非常显著。

 

     具体资料参考之前的笔记《关于 MySQL 索引的知识整理》     

  

     全文索引

     全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。

     全文搜索和其他几类索引的匹配方式完全不一样。

     

     在相同的列上同时创建全文索引和基于值的 B-Tree 索引不会有冲突;

     全文索引适用于 MATCH AGAINST 操作,而不是普通的 WHERE 条件操作。

 

三、索引的优点

 

     // 这个没什么好记录的,数据大就是快,毋需置疑;

     最常见的 B-Tree 索引,按照顺序存储数据,所以 MySQL 可以用来做 ORDER BY 和 GROUP BY 操作。

     因为数据是有序的,所以 B-Tree 也就会将相关的列值都存储在一起。

     最后,因为索引中存储了实际列的值,所以某些查询只使用索引就能够完成全部查询。

 

     三大优点:

     1. 索引大大减少了服务器需要扫描的数据量;

     2. 索引可以帮助服务器避免排序和临时表;

     3. 索引可以将随机I/O变为顺序I/O;

 

     总的来说,只有当索引帮助存储引擎快速地找到记录带来的好处大于其带来的额外工作时,索引才是有效的。

     对于非常小的表,大部分情况下简单的全表扫描更高效。

     对于中到大型的表,索引就非常有效。但对于特大型的表,建立和使用索引的代价将随之增长。

 

四、前缀索引和索引选择性?

 

     // 前缀索引:比如某字符串字段必须使用到索引的话,可以索引字符串字段的前缀部分内容;

 

     有时候需要索引很长的字符列,这会让索引变得大且慢。

     通常可以使用索引开始的部分字符,这样可以节省索引占用的磁盘空间,提高索引效率,

     但这样会降低索引的选择性。

     索引选择性是指,不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值,

     范围从 1~#T 之间,索引的选择性越高则查询效率越高,

     因为选择性搞的索引可以让 MySQL 在查找过滤掉更多的行。

 

     找出合适的前缀,如:

     SELECT COUNT(*) AS cnt, LEFT(city, 7) AS pref

     FROM city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 10;

 

     如何创建一个前缀索引:

     ALERT TABLE city_demo ADD KEY (city(7));

 

     参考:

     http://www.cnblogs.com/gomysql/p/3628926.html

 

五、多列索引

    

     在多个列上建立多个单列索引并不能提高 MySQL 的查询性能。

 

     一些专家诸如“把 WHERE 条件里面的列都建上索引”这样模糊的建议,实际上这个建议是非常错误的。

     这样一来最好的情况下也只能是“一星”索引,其性能比起真正最优的索引可能差几个数量级,

     有时如果无法设计一个“三星”索引,那么不如忽略掉 WHERE 子句,

     集中精力优化索引列的顺序,或者创建一个全覆盖的索引。

 

     索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建立的很糟糕:

     1、当出现服务器对多个索引做相交操作时(通过有多个 AND 操作),

          通常意味着需要一个包含所有相关的多列索引,而不是多个单独的单列索引;

     2、当服务器需要对多个索引做联合操作时(通过多个 OR 操作),其中有些索引的选择性不高,

          需要合并扫描返回大量数据的时候;

   

     可以通过 EXPLAIN 查看索引及查询的情况,具体资料如下:

     http://www.cnblogs.com/-simon/p/5887428.html

 

六、如何选择索引列的顺序

 

     如何选择索引列的顺序有一个经验法则:将选择性最高的列放到索引最前列。

     如果不考虑排序和分组时,将选择性最高的列在前面通常是很好的。

     这种方式,作用只用于优化 WHERE 条件的查找。

 

     举个栗子:

          SELECT * FROM payment WHERE staff_id = 2 and customer_id = 584;

 

     是应该创建一个 (staff_id, customer_id) 索引还是应该颠倒一下顺序?

 

     1、查询 WHERE 条件分支对应的数据基数有多大:

          SELECT SUM(staff_id = 2), SUM(customer_id = 584) FROM payment

          ************* 1. row **************

          SUM(staff_id):7992

          SUM(customer_id):30

          

          根据前面的经验法则,应该讲索引列 customer_id 放到前面,因为对应条件的 customer_id 数量更小。

 

     从这个小案例开源看到经验法则和推论在多数情况下是有用的,但是注意不要假设平均情况下的性能,

     特殊情况可能会摧毁整个应用的性能。

     尽管关于选择性和基数的经验法则指的去研究和分析,

     但是不要忽视了 WHERE 子句中的排序、分组和范围条件等其他因素,这些因素可能对查询性能造成很大的影响。

 

 

七、聚簇索引

 

     InnerDB 下主键就是聚簇索引;

     聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。

     InnerDB 的聚簇索引实际上在同一个结构中保存了 B-Tree 索引和数据行。

 技术分享

 

     在 InnerDB 引擎中如果没有定义主键,InnerDB 会选择一个唯一的非空索引代替。

     如果没有这样的索引,InnerDB 会隐式定义一个主键来作为聚簇索引。

 

     聚簇索引的一些重要优点:

     1、可以把相关数据保存在一起。例如实现电子邮件时,可以根据用户 ID 来聚集数据,

          这样只需要从硬盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引,

          则每封邮件都可能导致一次磁盘 IO。

     2、数据访问更快。(聚簇索引将索引和数据保存在同一个 B-Tree 中)

     3、使用覆盖索引扫描的查询可以直接使用页节点中的主键值。

 

     聚簇索引的缺点:

     1、聚簇数据最大限度的提高了 IO 密集型应用的性能;

     2、插入速度严重依赖于插入顺序;

     3、更新聚簇索引的代价很高,因为会强制 InnerDB 将每个被更新的行移动到新的位置;

     4、聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏或数据不连续的情况;

     5、二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列;

     6、二级索引访问需要两次索引查找,而不是一次;

 

 技术分享

 

八、总结

 

     在选择索引和利用这些索引编写查询时,有三点原则始终要记住:

     1、单行访问是最慢的。特别是在机械硬盘中(SSD 随机 I/O 要快很多,不过这一点仍然成立)。

          如果服务器从存储上读取数据块只为了获取其中一行,

          那么就浪费了很多工作,最好读取的块中能包含尽可能多行需要的行。

 

     2、按顺序访问范围数据是很快的,两个原因:

          a、顺序 I/O 不需要多次硬盘寻道,所以比随机 I/O 要快很多(特别是机械硬盘);

          b、如果服务器能够按照顺序读取数据,那么久不再需要额外的排序操作了,

               并且 GROUP BY 查询也无须再做排序和将行按进行聚合运算了。

          c、索引覆盖查询时很快的,如果一个索引包含了查询需要的所有列,

               那么存储引擎就不需要再回表查找行,这避免了大量的单行访问;

 

     总之编写查询语句尽可能选择合适的索引以避免单行查找。

 

   // 从印象笔记中导入,格式排版稍有偏差。

 

MySQL 索引知识整理(创建高性能的索引)