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支持前缀索引,只对列的前一部分内容做索引,能显著减少索引值大小。

sql 复制代码
alter table post add index idx_url(url(100));

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

sql 复制代码
select count(distinct substr(url, 1, 10)) / count(1) from post;
-- 0.5806

多列索引

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

sql 复制代码
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都能击中索引。

ini 复制代码
**索引合并**
索引合并支持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字段进行过滤,然后再去回表查询。

聚簇索引

Innodb里,数据是随着主键存储,所以主键索引就是聚簇索引。在未设置主键时,系统会默认添加一个6字节的自增主键。这个自增锁是全局公用,所以会有性能问题。如果有not null的唯一索引,就会用这个索引当做主键,就不会自动创建主键。

主键值最好是连续的,它有这些好处

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

也会存在一些问题

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

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

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

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

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

影响使用索引的情况

虽然在执行计划里显示会使用索引,但实际情况查询速度还是很慢,以下这些因素会影响索引使用。

数据频繁更新

对于二级索引,没有MVCC机制,为了保证索引数据准确性,Mysql会在更新事务提交的时候,把二级索引上的旧记录标记为删除,然后插入一条新数据。在其他进行中的事务读到删除的数据时,就要回表根据MVCC的逻辑查一下数据真实的状态,如果删除了,就不返回。当索引里很多满足的记录都被更新时,那就都要回表查询,这个过程就会影响索引的效率。

为了缓解这个问题,Mysql在二级索引上记录了当前已提交的最大事务ID(MAX_TRX_ID),当查询索引的事务视图中,最消活跃ID大于MAX_TRX_ID,说名事务开启的时候,对这条索引的修改已经提交了,就不要去检查删除的索引数据。

损坏的索引文件

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

查询未使用的索引

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

索引的构建

数据库以排序索引的方式构建btree索引,构建过程分为三步

  1. 扫描聚簇索引获取需要的列信息,并保存到排序缓冲区中。当缓冲区满时,会将数据排序后写入到临时文件。
  2. 将多个临时文件的数据合并排序,得到一个总的索引序列。
  3. 将排序后的数据按顺序插入到btree末尾,这个过程是多线程的。

排序索引对比之前的方式的有点是效率更高,原有的索引构建方式是查出一条数据,就插入btree随机的位置,这会导致索引页频道的分裂和合并。

空间索引使用的是R树,不支持排序索引。

相关推荐
drebander11 分钟前
MySQL 查询优化案例分享
数据库·mysql
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
18号房客3 小时前
高级sql技巧进阶教程
大数据·数据库·数据仓库·sql·mysql·时序数据库·数据库架构
翔云1234564 小时前
MySQL purged gtid是如何生成和维护的
数据库·mysql
平行线也会相交6 小时前
云图库平台(三)——后端用户模块开发
数据库·spring boot·mysql·云图库平台
恒辉信达7 小时前
hhdb客户端介绍(53)
数据库·mysql·hhdb·数据库可视化界面客户端
Hello.Reader8 小时前
Redis热点数据管理全解析:从MySQL同步到高效缓存的完整解决方案
redis·mysql·缓存
是程序喵呀9 小时前
MySQL备份
android·mysql·adb
指尖上跳动的旋律9 小时前
shell脚本定义特殊字符导致执行mysql文件错误的问题
数据库·mysql
苹果醋313 小时前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx