导读
假设这个用户中心系统是一个交友平台的一个子系统,现在产品想要提供一个按生日区间筛选用户的功能。那么,要实现这个功能,我们需要写这么一条SQL:
SELECT
为了保证这条SQL的查询性能,我们会给用户表中的birthday字段添加索引。乍一看最初的表结构设计,好像已经有一个索引index_age_birth,这个索引包含了birthday字段,是不是可以用这个现成的索引,不再另加索引?
对MySQL有一些基本了解的开发同学知道MySQL InnoDB的索引查找是按照最左前缀匹配原则的,即SQL中的Where条件中的字段,如果要命中索引,必须按照该索引列的顺序逐一比对,最后定位查找结果。index_age_birth这个索引列的顺序为,明显age在前,birthday在后,所以,不满足最左前缀匹配原则,无法命中该索引。
所以,现在我们必须给birthday这个字段单独加上索引,见下图:
ALTER
这时,你可能想到birthday这个字段,我们设的默认值为NULL,随之会产生一个疑问:如果某一个字段为NULL,以该字段作为条件进行查询,是否会影响我们查询的性能?今天这个章节,我们就先看看NULL这个值在InnoDB索引结构中是怎么存储的,然后,结合上面这条select查询SQL,看看MySQL又是如何执行这条SQL的,最后给到这个问题的答案。
存储结构
由于之前的用户表记录中没有birthday为NULL的记录,为了讲解NULL值对SQL查询性能的影响,我先添加一条birthday为NULL的记录,如下:
INSERT
通过《基础篇》中,我对InnoDB索引结构的讲解,我们知道我加的这个index_birthday索引是一个辅助索引,所以,我们就来看一下NULL这个值在该辅助索引的结构是什么样的,如下图:
上图就是一颗birthday字段为索引的B-Tree,辅助索引的B-Tree结构,我在《基础篇》中详细讲解了,大家可以对照之前的讲解看下这张图,我在这里主要说一下NULL值的位置:NULL值被存储到了该辅助索引B-Tree的非叶节点页1、页2和叶子节点页4的最左边。也就说NULL记录总是出现在B-Tree的最左侧。
那么,针对本章《导读》中的这条select语句,MySQL又是如何查找索引的呢?为了方便浏览,我在这边再贴一下这条SQL:
SELECT
查找过程
在前面的章节,我讲解过了MySQL查找辅助索引的整个过程,那么,结合这个例子,我再讲解一下MySQL是如何在index_birthday这个索引中查找[2007-01-02,2008-08-02]之间的记录的,见下图:
如上图,红色箭头部分,深度遍历这颗B-Tree:
- 页1 -> 页3,在页1中,发现2007-01-02大于2006-07-01,所以,箭头流向指向页3。
- 页3 -> 页6,发现2007-01-02位于[2006-07-01,2007-06-07]之间,所以,箭头流向指向页6。
- 由于页6为叶子节点,而辅助索引B-Tree所有节点内的记录按索引列升序排列,叶子节点之间是双向链表,叶子节点内记录组成单向链表,所以,发现页6中第一条大于等于2007-01-02的记录是<2007-06-06,6>,然后,从页6中的<2007-06-06,6>后开始,顺序遍历<2007-06-06,6>和页7的所有记录。
- 发现页7最后一条记录的age的值为2008-02-06,小于2008-08-02,因此,得到所有满足[2007-01-02,2008-08-02]之间的记录的主键6、8、2、5。
- 最后根据主键6、8、2、5,到聚簇索引中查询相应记录即可,关于详细查找过程,在这里我留一个悬念,在《IN字段查询多少个值最合适》这一章节中我会详细讲解。
小结
通过上述内容的讲解,我们知道了一张表中的一条记录中的某个字段a,它的值为NULL值,同时,a字段加了索引,那么
- a字段为NULL的记录一定出现在辅助索引非叶或叶子节点的最左边,采用深度遍历查找这条记录,效率是最高的。
- 查找a字段不为NULL的记录和NULL记录的数量无关,通过辅助索引B-Tree的二分查找是能很快定位到记录的。
所以,表结构中存在默认值为NULL的字段,并不会影响查询的性能。
思考
假设现在有这么两条记录,如下:
INSERT
如果现在我写了这样一条SQL:
SELECT
查找这两条记录的过程是怎么样的?
更多关于MySQL源码的解读内容,可以加vx群交流哦!或者知乎私信我,我都会回复的!
https://weixin.qq.com/g/AQYAAMmWP-ei65ZsYYGNtPd1Xt4-_tIcJO8jlAYRhlN1U1T0YdxXejTWCvh5X2sE (二维码自动识别)