MySQL索引原理深度剖析与优化

一、概念篇

索引是什么 ?

1. 官方介绍索引是帮助MySQL高效获取数据的数据结构。能加快数据库的查询速度。
2. 常见索引有聚集索引、覆盖索引、组合索引、前缀索引、唯一索引。

索引的优势和劣势

优势:

1. 可以提高数据检索的效率,降低数据库的IO成本,类似于书的目录。

2. 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

什么是IO?这里的IO是指?
劣势:

1. 索引会占据磁盘空间

2. 索引虽然会提高查询效率,但是会降低更新表的效率。
比如每次对表进行增删改操作,MySQL不仅要保存数据,
还有保存或者更新对应的索引文件。

二、B+结构数据结构篇

B+结构(改造B树)

B树:非叶子节点和叶子节点都会存储数据。
B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。
叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。

举个例子:

等值查询:

select * from table where id = 9;

过程如下图:
在这里插入图片描述

	查询路径磁盘块1->磁盘块2->磁盘块61. 第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,9<15,走左路,到磁盘寻址磁盘块22. 第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<9<12,到磁盘中寻址定位到磁盘块63. 第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,在第三个索引中找到9,取出data,
	如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,
	查询终止。(这里需要区分的是在InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址。)

范围查询

select * from table where id between 9 and 26;

在这里插入图片描述

	查找路径是磁盘块1->磁盘块2->磁盘块6->磁盘块71. 首先查找值等于9的数据,将值等于9的数据缓存到结果集。这一步和前面等值查询流程一样,发生了三次磁盘IO2. 查找到15之后,底层的叶子节点是一个有序列表,我们从磁盘块6,键值9开始向后遍历筛选所有符合筛选条件的数据。
	
	3. 第四次磁盘IO:根据磁盘6后继指针到磁盘中寻址定位到磁盘块7,将磁盘7加载到内存中,在内存中从头遍历比较,9<25<269<26<=26,将data缓存到结果集。
	
	4. 主键具备唯一性(后面不会有<=26的数据),不需再向后查找,查询终止。将结果集返回给用户。

【思考1】where name in(‘a’,‘b’,‘c’) ,name 为普通索引。会有多少次IO,随着in()内的字段值增多,IO性能影响?怎么优化?

三、索引实现篇

MyIsam索引

  1. 下面新建一张表如下:
CREATE TABLE `user`
(
  `id`       int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `age`      int(11)     DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_age` (`age`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

在这里插入图片描述

MyISAM的数据文件和索引文件是分开存储的。MyISAM使用B+树构建索引树时,叶子节点中存储的键值为索引列的值,数据为索引所在行的磁盘地址
【MyISAM数据存储了几个文件,分别存储了什么?innoDB呢?】

在这里插入图片描述
表user的索引存储在索引文件user.MYI中,数据文件存储在数据文件 user.MYD中。

简单分析下查询时的磁盘IO情况:

根据主键等值查询数据:

select * from user where id = 28;

1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO3. 检索到叶节点,将节点加载到内存中遍历,比较16<2818<2828=28。查找到值等于30的索引项。(1次磁盘IO4. 从索引项中获取磁盘地址,然后到数据文件user.MYD中获取对应整行记录。(1次磁盘IO)
将记录返给客户端。

磁盘IO次数:3次索引检索+记录数据检索。
在这里插入图片描述

根据主键范围查询数据:

select * from user where id between 28 and 47;

1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO3. 检索到叶节点,将节点加载到内存中遍历比较16<2818<2828=28<47。查找到值等于28的索引项。
根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO)
我们的查询语句时范围查找,需要向后遍历底层叶子链表,直至到达最后一个不满足筛选条件。

4. 向后遍历底层叶子链表,将下一个节点加载到内存中,遍历比较,28<47=47,根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO5. 最后得到两条符合筛选条件,将查询结果集返给客户端。

在这里插入图片描述

辅助索引

在 MyISAM 中,辅助索引和主键索引的结构是一样的,没有任何区别,叶子节点的数据存储的都是行记录的磁盘地址。只是主键索引的键值是唯一的,而辅助索引的键值可以重复。

查询数据时,由于辅助索引的键值不唯一,可能存在多个拥有相同的记录,所以即使是等值查询,也需要按照范围查询的方式在辅助索引树中检索数据。

InnoDB索引

主键索引(聚簇索引)

每个InnoDB表都有一个聚簇索引 ,聚簇索引使用B+树构建,叶子节点存储的数据是整行记录。一般情况下,聚簇索引等同于主键索引(二般情况是什么?聚簇索引定义是?),当一个表没有创建主键索引时,InnoDB会自动创建一个ROWID字段来构建聚簇索引。
InnoDB创建索引的具体规则如下:

  1. 在表上定义主键PRIMARY KEY,InnoDB将主键索引用作聚簇索引。
  2. 如果表没有定义主键,InnoDB会选择第一个不为NULL的唯一索引列用作聚簇索引。
  3. 如果以上两个都没有,InnoDB 会使用一个6 字节长整型的隐式字段 ROWID字段构建聚簇索引。该ROWID字段会在插入新行时自动递增。

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(不是数据结构,而是存储结构),具体细节依赖于其实现方式,但innodb的聚簇索引实际上是在同一个结构中保存了BTree(B-Tree ,B+Tree)索引和数据行

除聚簇索引之外的所有索引都称为辅助索引。在中InnoDB,辅助索引中的叶子节点存储的数据是该行的主键值。 在检索时,InnoDB使用此主键值在聚簇索引中搜索行记录。

InnoDB的数据和索引存储在一个文件t_user_innodb.ibd中。InnoDB的数据组织方式,是聚簇索引。

在这里插入图片描述

等值查询数据:

select * from user where id = 28;

1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO3. 检索到叶节点,将节点加载到内存中遍历,比较16<2818<2828=28。查找到值等于28的索引项,直接可以获取整行数据。将改记录返回给客户端。(1次磁盘IO

磁盘IO数量:3次。

在这里插入图片描述

辅助索引

除聚簇索引之外的所有索引都称为辅助索引,InnoDB的辅助索引只会存储主键值而非磁盘地址。

以表user_innodb的age列为例,age索引的索引结果如下图。

在这里插入图片描述
底层叶子节点的按照(age,id)的顺序排序,先按照age列从小到大排序,age列相同时按照id列从小到大排序。

使用辅助索引需要检索两遍索引:首先检索辅助索引获得主键,然后使用主键到主索引中检索获得记录。

图分析等值查询的情况
在这里插入图片描述
根据在辅助索引树中获取的主键id,到主键索引树检索数据的过程称为回表查询。【思考】那么什么情况下不会回表呢?

磁盘IO数:辅助索引3次+获取记录回表3次

组合索引

CREATE TABLE `abc_innodb`
(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a`  int(11)     DEFAULT NULL,
  `b`  int(11)     DEFAULT NULL,
  `c`  varchar(10) DEFAULT NULL,
  `d`  varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_abc` (`a`, `b`, `c`)
) ENGINE = InnoDB;

select * from abc_innodb order by a, b, c, id;

组合索引的数据结构:
在这里插入图片描述
组合索引的查询过程:
在这里插入图片描述
最左匹配原则:
最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。

在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,但是b列和c列是无序的,b列只有在a列值相等的情况下小范围内递增有序,而c列只能在a,b两列相等的情况下小范围内递增有序。

就像上面的查询,B+树会先比较a列来确定下一步应该搜索的方向,往左还是往右。如果a列相同再比较b列。但是如果查询条件没有a列,B+树就不知道第一步应该从哪个节点查起。

可以说创建的idx_abc(a,b,c)索引,相当于创建了(a)、(a,b)(a,b,c)三个索引。、

组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就停止匹配。

覆盖索引

覆盖索引并不是说是索引结构,覆盖索引是一种很常用的优化手段。因为在使用辅助索引的时候,我们只可以拿到主键值,相当于获取数据还需要再根据主键查询主键索引再获取到数据。但是试想下这么一种情况,在上面abc_innodb表中的组合索引查询时,如果我只需要abc字段的,那是不是意味着我们查询到组合索引的叶子节点就可以直接返回了,而不需要回表。这种情况就是覆盖索引。

四、优化篇

如何优化?平常建索引需要注意什么?

在InnoDB的存储引擎中,使用辅助索引查询的时候,因为辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引如果需要获取当前记录完整数据就必然需要根据主键值从主键索引继续查询。这个过程我们成位回表。想想回表必然是会消耗性能影响性能。那如何避免呢?

使用索引覆盖,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)

如果在一个场景下,select id,name,sex from user where name =‘zhangsan’;这个语句在业务上频繁使用到,而user表的其他字段使用频率远低于它,在这种情况下,如果我们在建立 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是不是根据辅助索引查询到的结果就可以获取当前语句的完整数据。这样就可以有效地避免了回表再获取sex的数据。

这里就是一个典型的使用覆盖索引的优化策略减少回表的情况。

联合索引的使用
联合索引,在建立索引的时候,尽量在多个单列索引上判断下是否可以使用联合索引。联合索引的使用不仅可以节省空间,还可以更容易的使用到索引覆盖。试想一下,索引的字段越多,是不是更容易满足查询需要返回的数据呢。比如联合索引(a_b_c),是不是等于有了索引:a,a_b,a_b_c三个索引,这样是不是节省了空间,当然节省的空间并不是三倍于(a,a_b,a_b_c)三个索引,因为索引树的数据没变,但是索引data字段的数据确实真实的节省了。

联合索引的创建原则,在创建联合索引的时候因该把频繁使用的列、区分度高的列放在前面,频繁使用代表索引利用率高,区分度高代表筛选粒度大,这些都是在索引创建的需要考虑到的优化场景,也可以在常需要作为查询返回的字段上增加到联合索引中,如果在联合索引上增加一个字段而使用到了覆盖索引,那我建议这种情况下使用联合索引。

联合索引的使用

考虑当前是否已经存在多个可以合并的单列索引,如果有,那么将当前多个单列索引创建为一个联合索引。
当前索引存在频繁使用作为返回字段的列,这个时候就可以考虑当前列是否可以加入到当前已经存在索引上,使其查询语句可以使用到覆盖索引。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值