Mysql索引总结

总结一下Mysql Innodb索引相关的知识,索引是以空间换时间的方式来加快查询速度。本质是将查询涉及的字段单独拎出来减少查询的基数和减少磁盘io次数,先做了排序可以用更高效的查询算法。

同时索引也有一些弊端,需要占用额外的空间,还不少。数据写入时,维护索引的数据排序,要消耗cpu。随着索引数量增加,查询优化器要评估每个索引的效率,对于用不到的索引的评估时间,影响查询效率。

索引分类

主键索引

primary key标注的列会被设置表的主键,可以是多个字段作为联合主键。Innodb的会按照主键顺序将记录组织成b-tree(实际是b+tree,统称为b-tree)形式。叶子结点存放记录数据,非叶子节点存放主键指作为索引的key。这棵b-tree就是主键索引。

普通索引

普通索引的结构也是b-tree,只是叶子结点存放的值不是记录,而是主键的值。所以,普通索引的使用流程是,现在索引上进行过滤,找到主键后,去主键索引里找到记录,这个过程称为回表查询。

唯一索引

唯一索引是在普通索引的基础上,加了唯一的约束,会在记录插入时,判断记录中包含的字段是否存在,存在则不允许插入。在查询的用法上跟唯一索引一样。

索引结构类型

B-tree索引

Innodb是用b+tree形式组织索引数据,b+tree是一颗多叉树,非叶子结点存放索引字段的值,叶子结点存放主键/记录。叶子结点有一个指针指向下一个叶子结点,提供范围查询的效率。

Hash索引

哈希索引将索引列以哈希表的形式存放,适合单值查询,不适合范围查询,所以使用的不多。Innodb有一个自适应哈希索引,会对索引上频繁访问的值,会在原有b-tree的索引之上,在内存中自动建立哈希索引,提高查询效率。

怎么使用索引

索引使用的三个原则

  1. 单行访问是慢的,一次磁盘io会获取一页数据,如果查询只用到数据页里的一行数据,性价比就很低。
  2. 按顺序查询数据是很快的,因为有机会用到顺序io,速度很快;另外顺序查询免去了排序的消耗。
  3. 索引覆盖查询很快,因为不需要回表,避免了单行访问。

所以最理想的情况是,用索引查询连续的结果集。

索引的三星原则

  1. 将查询相关数据放到一起
  2. 索引数据顺序和查询顺序一致,使order by能用到索引
  3. 索引的接包含查询中需要的全部列

实例要看查询优化器的结果,用explain判断索引是否生效。

索引使用方式

前缀索引

当索引的列值很大,例如对url加了索引,一个磁盘页(16kb)能存的索引记录数就越少,查询索引就需要读取更多的磁盘页,降低查询效率。

Innodb支持前缀索引,只对列的前一部分内容做索引,能显著减少索引值大小。

alter table post add index idx_url(url(100));

使用前缀索引时,要保证放入索引的部分内容的选择性。选择性越高,使用索引一次判断能过滤掉的记录数就越多,查询的效果就越好。选择性的计算公式一般是:

select count(distinct substr(url, 1, 10)) / count(1) from post;
-- 0.5806

多列索引

当一次查询用到多个字段时,我们可以对查询列组合起来建立一个索引,这样的索引叫多列索引或联合索引。

alter table post add index idx_title_author(title, author);

多列索引在能解决单列索引的很多问题,当然它也有一些自身的问题。我们先看下单列索引有什么问题。

工作中碰到一个同事,他把表的每个字段都加上索引,说这样在任何查询至少能用到1个索引。这样的方式很简单,但往往不是最优的方式。

以select id from post where category = ’game’ and hot = 100,在category和hot上都有索引。如果索引只命中了category一个索引,就需要回表查询hot=100,回表的操作是随机io,可能比顺序的全表扫描还慢。当然,mysql会使用一种索引合并的技术,使用表中多个单列索引来定位记录。这种情况下,category和hot都能击中索引。

**索引合并**
索引合并支持or、and和混合的情况。使用explain能看到type=index_merge,并且能在extra里看到using union(idx_category, idx_hot)等信息。
索引合并事实上不是一个好的选择。对于and的情况出现合并索引,最好是改成多列索引。对于or的情况,在对数据进行排序和合并时,合并算法会消耗大量cpu和内存。并且查询优化器还看不到这部分的消耗,导致实际查询成本比优化器提示的成本要高很多。
所以,尽量别用索引合并的功能,可以通过optimizer_switch来关闭该功能。

索引列的顺序

多列索引对索引列要满足最左匹配原则,即左边的列先满足查询条件,才会用上下一个列。并且,前面列的选择性越好,过滤掉越多的记录,那下一个列需要过滤的基数就越少。所以,我们一般会将选择性最高的列放在最左边。

但也会有特殊的情况,例如select id from post where author_id = 1 and category = ’game’; author_id值区间是1-6,1是管理员账号,category值有5种。现在author_id 2-6的人在不同category下发了一片文章,author_id为1的管理员也在不同category下发了一片文章。这时候author_id的选择性是6/10=0.6,category的选择性是5/10=0.5,按照经验是把author_id放在多列索引的前面。而上面这条查询,先查author_id后会返回5条记录,而现查category后会返回2条记录,所以category先查会更优一点。

上面这个情况是个特例,用来说明对索引顺序的选择要结合实际情况,但系统往往是被这种特殊的情况而击垮。

另外,是否要把单列索引尽可能换成多列索引呢?

当然不是,使用多列索引是有成本的,多列索引在多值查询的时候有收益,但是对于单值查询来说,就有损耗了。因为多列索引的key会变长,并且b-tree节点数变多,会让索引的体积变大。那么单列查询的性能就降低了。

隐藏的多列索引

由于非聚簇索引的叶子节点是主键id,并且id值是有序的,所以非聚簇索引默认和id组合成了多列索引。例如单列索引idx_author(author),它实际的效果跟idx_author(author, id)是一样的,多列所有也是一样的道理。

覆盖索引

索引的列包含查询所需的所有信息时,包括select、where、order by、group by子句涉及的列,这时不需要二次回表,这样的索引称为覆盖索引。

覆盖索引拥有非常优秀的性能,在查询当击中时,explain的extra字段会包含using index值。

使用索引来排序

Mysql索引除了能用来过滤记录,还能用在order by帮助排序。当排序击中索引时,explain的type=index;未击中索引时,extra字段会保护using filesort。

使用索引进行排序有几个条件:

  1. 索引的顺序和order by子句的顺序完全一样,并且所有列的排序方向都一样
  2. 联合查询语句,order by引用的列要全部在第一张表中
  3. 要满足索引最左前缀的要求

当然,并不是满足条件就会使用索引排序,还要根据查询优化器的总和评估成本。例如,排序完还需要回表查询其他字段,当回表次数多时,就不会使用索引信息,直接通过全表扫描后再排序。

索引下推

在mysql5.6开始,在索引遍历的过程中,优先用索引的字段进行过滤,减少回表次数。例如查询语句a like ‘x%’ and b = ‘y’ and c = ‘z’的时候,联合索引(a,b)根据最左匹配原则只能用到索引a字段,然后就需要回表查询到b字段再进行比较。有了索引下推,就能优先使用索引里的b字段进行过滤,然后再去回表查询。

聚簇索引

主键值连续且自增的好处

  1. 按顺序添加数据页,新数据都在新的数据页,可以一次性写入磁盘,性能更好。
  2. 可以避免数据页的分裂,减少数据碎片,占用索引空间更小。

缺点

在高并发的场景会有写入竞争。对主键上界会成为热点。会导致间隙锁的竞争。如果用到了自增,会对自增锁有竞争。

自增锁可以通过修改自增算法来优化,修改innodb_autoinc_lock_mode的值:

0:传统模式,持有自增锁,执行完语句后释放锁。

1:连续模式,持有mutex锁,在明确插入记录数 情况下,拿到对应数量的值就释放锁。

2:交叉模式,持有mutex锁,可以并发过去值。对于binlog等于statement的同步方式,可能造成master和slaver的值不一样。

查询未使用的索引

通过sys.table_io_waits_summary_by_index_usag找到未使用的索引,及时将无效的索引删除

损坏的索引文件

当索引文件因为各种原因会出现损坏的情况,就会导致出现莫名其妙的问题了,返回错误的结果或出现主键冲突等。这时候可以通过check table命令检查表索引是否损坏,然后通过repair table命令修复表索引。有的引擎不支持该命令,也可以通过没有操作的alter命令来重建索引,例如alter table user engin=innodb;

你可能感兴趣的:(关系型数据库,Mysql,mysql,数据库,索引)