一、索引基础
如果索引包含多个列,那么列的顺序十分重要,因为mysql只能高效的使用所有的最左前缀列。
在mysql中索引是在存储引擎层实现的,而不是在服务器层。
MyISAM使用前缀压缩技术,使得索引更小,但是InnoDB则按照原数据格式进行存储;MyISAM可以通过数据的物理位置(指针)引用被索引的行,而InnoDB则根据主键引用被索引的行(指InnoDB的二级索引)。
-
B-Tree对数据是顺序组织存储的,所以很适合查找范围数据。
索引对多个值进行排序的依据是CREATE TABLE语句中定义索引时列的顺序,如上图最后两个条目,两个人的姓和名都一样,则根据他们的出生日期来排序 -
索引对如下查询有效:
- 全值匹配,指和索引中的所有列进行匹配。
- 匹配最左前缀。
- 匹配列前缀,即只匹配某一列的值得开头部分。如 like "abc%"(注意,通配符%不能放在最前面)。
- 匹配范围值。
- 精确匹配某一列并范围匹配另外一列
- 只访问索引查询。B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无需访问数据行,这种查询叫做“覆盖索引”,还有一种查询叫做“回表查询”
-
关于B-Tree索引的限制:
如果不是按照索引的最左列开始查找,则无法使用索引。
不能跳过索引中的列。如存在(a,b,c)这样的索引,查询条件中只有a、c,则在查询过程中Mysql只能使用索引的a这一列。
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查询。如,存在索引(a,b,c),查询语句为 xxxx WHERE a='123' AND b LIKE 'ww%' AND c='312' ,这个查询只能使用到索引的前两列。(大于、小于、等于等操作也是范围查询)
哈希索引。存储引擎都会对所有的索引列计算一个哈希码,哈希码索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每一个数据行的指针。
-
哈希索引的限制:
- 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。
- 哈希索引数据并不是按照索引值顺序存储的,所以无法用于排序。
- 哈希索引也不支持部分索引列匹配查找。
- 哈希索引只支持等值比较,如=、IN、<=>,不支持范围查询。
InnoDB引擎有一个特殊的功能,叫做“自适应哈希索引”。即,当InnoDB注意到某些索引值被使用的非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点。这是一个完全自动的、内部的行为,用户无法配置,但是也可以关掉。
二、索引的优点
-
索引有如下三个优点:
- 索引大大减少了服务器需要扫描的数据量
- 索引可以帮助服务器避免排序和临时表
- 索引可以将随机IO变为顺序IO
最常见的B-Tree索引,按照顺序存储数据,所以MySQL可以用来做ORDER BY和GROUP BY操作。
-
“三星系统”的概念:
- 索引将相关的记录放到一起则获得一星;
- 如果索引中的数据顺序和查找中的排列顺序一致,则获得二星;
- 如果索引中的列包含了查询中需要的全部列则获得“三星” 。
三、高性能的索引策略
对于特大型的表,建立和使用索引的代价将随之增长。这种情况下,则需要一种技术可以直接区分出查询需要的一组数据,而不是一条记录一条记录地匹配(即,分库、分表、分区技术)。
如果在查询过程中索引列是表达式的一部分或参数,则整个查询在执行的过程中不会使用索引。如actor_id是索引:“select actor_id from tb where actor_id+1>10; ”
列区分度 = 不重复的列的行数 / 数据表总行数。一般情况下,区分度高的列适合做索引,因为当使用这些列进行检索时,可以过滤掉更多的行。
对于联合索引,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行扫描。
where子句中的排序、分组和范围条件等其他因素,都可能会对查询的性能造成非常大的影响。
聚簇索引。它的数据行实际存在在索引的叶子节点上。
InnoDB通过主键索引是聚簇索引。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式定义一个主键索引来作为聚簇索引。
-
聚簇索引的优点:
- 可以把数据保存在一起
- 数据访问更快
- 用主键为唯一查询条件时,可以实现索引覆盖扫描。
-
聚簇索引的缺点:
聚簇数据最大限度地提高了IO密集型应用的性能,但是如果数据全部都放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了。
插入速度严重依赖于插入顺序。
更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
基于聚簇索引的表在插入新行,或者主键被更新导致需要移动数据的时候,可能面临“页分裂”的问题。即,当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该也分裂成两个页面来容纳该行,这就是一次页分裂操作。【这一点和前两点说的基本上都是一回事】
主键索引列顺序插入很重要,如果主键列是按序递增的,那么在生成主键聚簇索引时,总会把新值插入到索引的最后,因此就不会出现页分裂等情况。当不按照顺序插入时,经常要为新的值寻找合适的位置(通常是已经有数据的中间位置),从而增加很多额外的操作(如分配新的空间,数据移位等)。因此一般情况下,主键列建议使用自增】
频繁的页分裂操作,会使得页变得稀疏并被不规则的填充,最终数据会有碎片。
二级索引的叶子节点是主键值(不是指针),因此当通过二级索引查询二级索引中不包含的列时,需要回表查询(即,通过二级索引查找到对应的主键,再通过主键索引定位最终的列)。
-
如果一个索引包含了所有需要查询的字段的值,我们就称之为“覆盖索引”。即,只需要扫描索引树就可以完成数据的查询。如,一个表有id、name、sex三个字段,其中id是主键、name列为索引:
- 当 以“select id, name from tb where name='xxx' ”进行查询时是覆盖索引,因为name是索引列,id是索引叶子节点,因此只需要扫描name索引树即可获取结果。
- 当执行“ select id,name,sex from tb where name='xxx' ”时,就需要回表查询,因为name索引树中不包含sex列。
在执行查询时,Mysql会根据select列和where子句字段来决定如何使用索引。【因此在生产环境中尽量不要使用select * 去查询】
当一个SQL是“覆盖索引”查询的时候,在explain的extra列可以看到“Using index”
在索引列上使用like进行查询的时候,如果是通配符开头的like查询,无法使用到索引。即 xxx like '%asx%'不会使用索引,xxx like 'as%'可以。
MySQL有两种方式可以生成有序的结果:一种是通过排序操作;一种是按照索引顺序扫描 。如果explain出来的type列的值为“index”,则说明MySQL使用了索引扫描来排序。(注意不要和“Using index”混淆了)
MySQL可以使用同一个索引既满足排序,又用于查找行。因此,如果可能,设计索引时应尽可能地同时满足这两种任务。
-
【重要】关于使用索引排序几个重要的点(order by和group by都适用):
只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的顺序方向(倒序或正序)都一样时,Mysql才能够使用索引来对结果做排序。
如果查询需要关联多张表,则只有当order by子句引用的字段全部为第一个表时,才能使用索引做排序。如“ SELECT tb1.a,tb2.b FROM tb1 JOIN tb2 ON tb1.c=tb2.c ORDER BY tb1.a, tb2.b”这样的查询,在order by子句中使用的字段来自tb1和tb2,这样排序就无法通过使用索引来实现。
order by子句和查找型查询的限制一样,需要满足索引的最左前缀的要求才能利用索引进行排序。
有一种情况下order by子句即使不满足索引的最左前缀的要求,也可以利用索引进行排序,那就是前导列为常量时候。如一个表中有(a,b,c)联合索引,当执行“ xxxxx where a=123 order by b,c”时,前导列a是常数,因此可以使用索引进行排序。
-
几个示例,如一个表中有(a,b,c)联合索引:
[1] 【xxxx where a>123 order by a, b】 可以利用索引排序,因为order子句使用的两列符合索引的最左前缀。
[2] 【xxxx where a=123 order by b desc, c asc】 无法利用索引排序,order by子句列的排序方向不一致。
[3] 【xxxx where a=123 order by b, d】 无法利用索引排序,d不在索引列中。
[4] 【xxxx where a=123 order by c】 无法利用索引排序,只是用了a和c无法组成索引的最左前缀。
[5] 【xxxx where a>123 order by b,c】 无法利用索引排序,a列是范围查询,无法通过最左前缀来利用索引。
[6] 【xxxx where a=123 and b in ('y','x') order by c】 无法利用索引排序,b列上有多个等于条件,对于排序来说这是一种范围查询,因此无法利用最左前缀来进行排序。
MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放进内存中,这在某些情况下能极大的提高性能。其压缩方法是,先完全保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同的前缀的字节数和剩余的不同后缀部分,把这部分存储起来即可。例如:索引块中第一个值是“perform”,第二个值是“performance”,那么第二个值的前缀压缩后存储的是类似“7,ance”这样的形式。
因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找时无法在索引块使用二分查找,而只能从头开始扫描。
大多数情况下都不需要冗余的索引,应该尽量扩展已有的索引而不是创建新的索引。若存在重复的索引,mysql优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能。但有时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他使用该索引的查询的性能。
索引可以让查询锁定更少的行。
MySQL在执行查询时,如果无法通过索引过滤掉无效的行,那么在InnoDB检索到数据并返回执行引擎层后,执行引擎通过where子句对数据进行过滤,此时,explain的extra列会出现“Using where”。
当设计索引时,不要只为现有的查询考虑需要哪些索引,还需要考虑对查询进行优化。如果发现某些查询需要新的索引,但是这个索引又会降低另一些查询的效率,那么应该想一下,是否能优化原来的查询。应该通过优化索引和查询以找到最佳的平衡,而不是闭门造车去设计最完美的索引。
尽可能将需要做范围查询的列放到索引的最后。
查询中避免多个范围条件,如果MySQL使用了某个索引进行范围查询,就无法再使用另一个索引(或者是该索引的后续字段)。