从根上彻底理解MySQL的索引( 三 )


从根上彻底理解MySQL的索引

文章插图
 
对应到存储结构上那就是下图:
从根上彻底理解MySQL的索引

文章插图
 
按照上图,我们又添加了一个数据页99,用来保存页32和页124对应的2条目录,现在要查找主键ID为14的记录,需要经历这几个步骤:
  1. 就从页99中,快速检索到对应的目录项数据页124;
  2. 在页124中,快速检索到对应的数据页27;
  3. 在页27中,快速检索到主键为14的记录 。
到这里为止,你已经悄悄地掌握了B+树了 。没错,上面我们一步步推导出来的搜索结构就是大名鼎鼎的B+树,而MySQL给它起了一个更响亮的名字——索引 。
B+树最底层的节点(对应图中存储用户记录的数据页)被称为叶子节点,其他的节点自然叫做非叶子节点了,更特殊地,B+树最顶部的节点叫做根节点 。
有一个值得我们关注的细节,这棵B+树的叶子节点存储了我们完整的用户记录(就是我们插入表的所有数据),而且,这是用户记录在InnoDB引擎中的唯一存储方式 。也就是所谓的“索引即数据,数据即索引” 。
更方便地一点是,这个关于主键的索引完全是由InnoDB存储引擎自动生成的,不需要我们显式地书写创建索引的语句 。这个索引叫做主键索引,又叫做聚簇索引 。
主键索引有两个特点:
  1. 按照主键的大小对用户记录和数据页进行排序,记录用单向链表连接,数据页使用双向链表连接;
  2. B+树的叶子节点保存了用户的完整记录 。
现在终于解释完为什么主键查询这么快了,搞明白主键索引之后,普通索引和联合索引就太简单了!
3.2 普通索引主键索引是在搜索条件为主键的时候才会发挥作用,但是我要以name='蝉沐风'为搜索条件怎么办?通过主键索引的讲解,我们首先会想到这么一个方案:再创建一个B+树(我们称为name索引),其中用户记录和数据页按照name字段进行排序,B+树的叶子节点保留完整的用户数据,这样就可以实现对name列的快速搜索了 。
但是如此一来,表中数据就被完整记录了2次(主键索引的叶子节点和name索引的叶子节点),要是我们为其他字段再建立索引,磁盘空间可想而知 。因此,我们得想个其他的办法 。
我们已经知道根据主键查询用户记录是非常快的了,那我们可以想个办法根据name字段来迅速找到主键,然后再根据主键查找用户记录啊 。这个办法同样离不开B+树 。
从根上彻底理解MySQL的索引

文章插图
 
这棵B+树和聚簇索引的B+树有点区别:
  1. 叶子节点存放的不再是完整的用户记录,而是只记录name列和主键值;
  2. 数据页中存放的用户记录和目录项记录由原本的按照主键排序变为按照name列排序;
  3. 目录项记录除了存储索引列(name)和页号之外,同时还存储了主键值;(大家可以想一想,为什么要存储主键值)
有了这棵B+树,你就可以通过name列快速找到主键值了,查找的方式和根据主键值查找用户记录的方式完全一样,只不过前者查到的是主键值,后者查找到的是一条完整的用户记录罢了 。
你可能对字符串进行二分法感到有点奇怪,甚至没有接触过的相关知识的读者连对字符串进行排序都会觉得很诧异 。其实在创建表的时候我们可以对字符串字段指定字符集和比较规则,如果你不指定,MySQL会默认给你设置,总之,MySQL总会找到一个方式对字符串进行排序 。
现在得到主键的id了,然后根据主键id到主键索引中查找到完整的用户记录,这个过程叫做回表 。如果没有为name列设置唯一性约束,那就可能找到多个符合条件的主键id,多回几次表就可以了 。
对name这种单个列添加的索引叫做普通索引,也叫二级索引 。
如果同时对多个列建立索引,那B+树的存储又会是什么样子呢?这就是联合索引了,理解了上面的内容,再理解联合索引只是水到渠成的事罢了 。
3.3 联合索引假设我们为name列和phone列建立联合索引(注意我描述的顺序),自然也是创建一棵B+树,这棵B+树和之前又稍微有点不同:
  1. 叶子节点存放的是name列、phone列和主键值;
  2. 目录项记录除了存储索引列(name、phone)和页号之外,同时还存储了主键值;(大家可以想一想,为什么要存储主键值)


    推荐阅读