从根上彻底理解MySQL的索引

这是图解MySQL的第4篇文章,这篇文章会让你

  • 明白什么是索引,彻底理解B+树和索引的关系;
  • 彻底理解主键索引、普通索引、联合索引;
  • 了解什么是HASH索引,InnoDB和MyISAM索引的不同实现方式;
  • 轻松理解后续的索引使用规则 。
 
1. 准备工作为了更好地解释索引,我们先建个表 。
CREATE TABLE `user_innodb` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`gender` tinyint(1) DEFAULT NULL,`phone` varchar(11) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;我创建了一个存储引擎为InnoDB的表user_innodb,其中包含主键id、姓名字段(name)、性别字段(gender,用0,1表示不同性别)、手机号字段(phone),并批量初始化了500W+条数据 。
注:数据全部是模拟产生的,性别不做严格区分;手机号如有雷同,纯属巧合
mysql> SELECT COUNT(*) FROM user_innodb;+----------+| COUNT(*) |+----------+|5283424 |+----------+1 row in set (0.31 sec)
从根上彻底理解MySQL的索引

文章插图
 
例1:为name创建索引之前
mysql> SELECT * FROM user_innodb WHERE name = "蝉沐风";+---------+-----------+--------+-------------+| id| name| gender | phone|+---------+-----------+--------+-------------+| 1099999 | 蝉沐风|0 | 13203398311 |+---------+-----------+--------+-------------+1 row in set (0.96 sec)例2:为name创建索引之后
mysql> SELECT * FROM user_innodb WHERE name = "蝉沐风";+---------+-----------+--------+-------------+| id| name| gender | phone|+---------+-----------+--------+-------------+| 1099999 | 蝉沐风|0 | 13203398311 |+---------+-----------+--------+-------------+1 row in set (0.03 sec)例3:根据主键id进行查询
mysql> select * from user_innodb where id = 1099999;+---------+-----------+--------+-------------+| id| name| gender | phone|+---------+-----------+--------+-------------+| 1099999 | 蝉沐风|0 | 13203398311 |+---------+-----------+--------+-------------+1 row in set (0.00 sec)可以看到,创建索引之前搜索name为蝉沐风的记录花费时间为0.96秒,为name字段创建索引后,搜索时间仅为0.03秒,可见索引的作用之大 。
但是我们没有显式为主键创建索引,为什么主键查询也这么快?我在上一篇文章中解释了主键查询快的原因,但是只解释了一半,现在我来解释另一半 。
虽然我希望每一篇文章都讲述一个独立的知识点,但是对于MySQL这种复杂的软件,各种细节之间盘根错节,想深入理解一个知识点很多时候需要其他知识点的加持,在继续阅读之前,强烈推荐你花10分钟先读一下这篇文章 。
如果你实在不想看,我会简单总结一下之前讲的内容 。
强烈推荐阅读:图解|12张图解释MySQL主键查询为什么这么快
2. 前置知识现在我们已经知道了,InnoDB存储引擎为我们提供了4种不同的行格式来保存我们向MySQL中插入的数据,在这里我们统一称之为记录 。
记录是保存在InnoDB页中的,InnoDB存储引擎将数据划分为若干个页,以页作为磁盘和内存之间交互的最小单位 。InnoDB中页的大小默认为16KB 。也就是默认情况下,一次最少从磁盘中读取16KB的数据到内存中,一次最少把内存中16KB的内容刷新到磁盘上 。存储用户记录的页我们统一叫做数据页,它只是众多类型的InnoDB页中的一种而已,其他类型的页我们无需关注 。
非常非常重要的一点是,在一个数据页中,用户记录是按照主键由小到大的顺序串联而成的单向链表 。
但是一个数据页中的记录可能非常多,为了逃避低效的遍历,InnoDB引擎的设计者想出了一种绝妙的搜索方法,把数据页中的所有记录(包括伪记录)分成若干个小组(并对每个小组内的组员数量做了规定),每个小组选出组内最大的一条记录作为“小组长”,接着把所有小组长的地址拿出来,编成目录 。
举个例子,下面的图片展示了一个数据页中的所有记录被分组的情况:
从根上彻底理解MySQL的索引

文章插图
 
上图中的所有记录(包括伪记录)分成了4个小组,每个小组的“组长”被单独提拔,单独编制成“目录”,InnoDB官方称之为「槽」 。槽在物理空间中是连续的,意味着通过一个槽可以很轻松地找到它的上一个和下一个,这一点非常重要 。
槽的编号从0开始,我们查找数据的时候先找到对应的槽,然后再到小组中进行遍历即可,因为一个小组内的记录数量并不多,遍历的性能损耗可以忽略 。而且每个槽代表的“组长”的主键值也是从小到大进行排列的,所以我们可以用二分法进行槽的快速查找 。


推荐阅读