先来看到题,建表语句:html
create table user( `id` bigint auto_increment COMMENT '主键ID', `age` int not null COMMENT '年龄', `name` varchar(1024) not null COMMENT '姓名', `country` varchar(1024) not null COMMENT '国家', `city` varchar(1024) not null COMMENT '城市', PRIMARY KEY (`id`), KEY `IDX_NAME` (`name`), KEY `IDX_AGE` (`age`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息';
除了主键外,创建了2个索引,name和age,那么下面两句sql分别会命中哪些索引呢?java
select * from user where name='yang' and age=10; select * from user where name='yang' or age=10;
若是对这道题没啥思路的,那么就往下看吧,看完本文后相信答案也就出来了。mysql
InnoDB是一个将表中的数据存储到磁盘上的存储引擎,因此即便关机后重启咱们的数据仍是存在的。而真正处理数据的过程是发生在内存中的,因此须要把磁盘中的数据加载到内存中,若是是处理写入或修改请求的话,还须要把内存中的内容刷新到磁盘上。而咱们知道读写磁盘的速度很是慢,和内存读写差了几个数量级,因此当咱们想从表中获取某些记录时,InnoDB存储引擎须要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB采起的方式是:将数据划分为若干个页,以页做为磁盘和内存之间交互的基本单位,InnoDB中页的大小通常为 16KB。也就是在通常状况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。sql
这里不对InnoDB的页作过多介绍,有兴趣的同窗能够研究这两篇文章:InnoDB记录存储结构,InnoDB数据页结构。数据库
为何须要索引呢?索引能够帮助咱们解决什么问题?数组
查字典你们都了解吧,碰到一个不认识的字,会用笔划的方式进行查询,以下图数据结构
其实这个目录就是索引,帮助你可以更快的按照某种规则查找到你须要的内容。试想下,若是没有这个『笔划索引』,若是要找一个字,怎么办?只能一页一页翻了,须要所有遍历。若是字典的排序是按照笔划数排序的,那么你还能用二分查找的方式,加速查找过程。哈哈,扯远了,回过来,索引的存在,其实就是将无序的数据变成有序(相对),提升检索速度。post
你们都知道,InnoDB的索引使用的数据结构是B+树,那么为何是B+树呢?在介绍B+树索引机制前,咱们先了解另外两种数据解构,哈希和B树。优化
说到索引,咱们很容易想到经过哈希的方式实现,时间复杂度O(1)。相信在你的平常业务代码中,应该会常常出现下面的代码,将一个list根据某个key转化为Map,其实就这个就是索引思想。ui
Map<Long, User> id2User = Maps.uniqueIndex(users, User::getId);
哈希索引示意图:
键值key经过Hash映射找到bucket。在这里bucket指的是一个能存储一条或多条记录的存储单位。一个桶的结构包含了一个内存指针数组,其中的每一行数据都会指向下一条,造成链表结构,当遇到Hash冲突时,会在桶中进行键值的查找。
采用Hash进行检索效率很是高,若是查找的字段创建了索引,那么基本上一次检索就能够找到数据。可是你们忽略了,查找场景并不仅有精确检索( where name=xxx ),还有范围检索、模糊检索等等。
Hash索引主要存在如下缺点:
如图所示,区别有如下两点:
B+树的优势:
B树的优势:
能够看到,B+树的两个优势对于提高DB检索效率都是很是有用的。第一个在必定大小的空间中能够存放更多索引,下降树的高度,即检索时间。第二个在对于范围扫描更加方便。
先看下mysql中一行记录的结构示意图
主要几个部分:
record_type
:记录头信息的一项属性,表示记录的类型,0
表示普通记录、1
表示目录项、2
表示最小记录、3
表示最大记录next_type
:记录头信息的一项属性,表示下一条地址的偏移量,为了方便你们理解,咱们都会用箭头来代表下一条记录是谁。各个列的值
:就是各个数据列的值,其中咱们用橘黄色的格子表明c1
列,深蓝色的格子表明c2
列,红色格子表明c3
列。其余信息
:除了上述3种信息之外的全部信息,包括其余隐藏列的值以及记录的额外信息。而后再看下单个页的结构示意图
3条普通记录,一条最小记录,一条最大记录,5条记录造成单向链表。
上面说的每一个页的大小是16KB,为了便于说明问题,假设每页只能放3条普通记录,增长记录就须要增长页,下面是多页结构示意图:
几点须要注意
由于这些16KB
的页在物理存储上并不挨着,因此若是想从这么多页中根据主键值快速定位某些记录所在的页,咱们须要给它们作个目录,每一个页对应一个目录项,每一个目录项包括下边两个部分:
key
来表示。page_no
表示。而后咱们看下如何寻找id=20的记录。
20
的记录在目录项3
中(由于 12 < 20 < 209
),它对应的页是页9
。页9
中根绝二分法快速定位具体的记录。这样是否是查找数据就快了?嗯,正是这个目录的功劳!对了,忘记说了,这个目录有个别名,叫索引。
下面再看一个高度为3的B+树索引:
从图中能够看出来,一个B+
树的节点其实能够分红好多层,设计InnoDB
的大叔们为了讨论方便,规定最下边的那层,也就是存放咱们用户记录的那层为第0
层,以后依次往上加。上边咱们假设一页只能放3条普通记录,其实真实环境中一个页存放的记录数量是很是大的,假设,假设,假设全部的数据页,包括存储真实用户记录和目录项记录的页,均可以存放1000
条记录,那么:
B+
树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放1000
条记录。B+
树有2层,最多能存放1000×1000=1000000
条记录。B+
树有3层,最多能存放1000×1000×1000=1000000000
条记录。B+
树有4层,最多能存放1000×1000×1000×1000=1000000000000
条记录。哇咔咔~这么多的记录!!!你的表里能存放1000000000000
条记录么?因此通常状况下,咱们用到的B+
树都不会超过4层,那咱们经过主键去查找某条记录最多只须要作4个页面内的查找,又由于在每一个页面内有所谓的Page Directory
(页目录),因此在页面内也能够经过二分法实现快速定位记录,是否是很高效!
下面看个例子,表建立语句以下:
create table user( `id` bigint auto_increment COMMENT '主键ID', `age` int not null COMMENT '年龄', `name` varchar(1024) not null COMMENT '姓名', `country` varchar(1024) not null COMMENT '国家', `city` varchar(1024) not null COMMENT '城市', PRIMARY KEY (`id`), KEY `IDX_NAME` (`name`), KEY `IDX_COUNTRY_CITY` (`country`, `city`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息';
能够看到一共创建了3个索引,主键id索引、name索引、country+city的联合索引。其中id就是汇集索引,name就是非汇集索引(二级索引)。
简单归纳:
区别:
以上有3个索引,因此会有3颗B+树
上面提到了联合索引,联合索引有一个最左匹配原则,介绍以下:
(a)
,也能够复杂如多个列(a, b, c, d)
,即联合索引。(>、<、between、like
左匹配)等就不能进一步匹配了,后续退化为线性查找。例子:
(a, b, c, d)
,查询条件a = 1 and b = 2 and c > 3 and d = 4
,则会在每一个节点依次命中a、b、c,没法命中d。(很简单:索引命中只能是相等的状况,不能是范围匹配)不须要考虑=、in等的顺序,mysql会自动优化这些条件的顺序,以匹配尽量多的索引列。
例子:
(a, b, c, d)
,查询条件c > 3 and b = 2 and a = 1 and d < 4
与a = 1 and c > 3 and b = 2 and d < 4
等顺序都是能够的,MySQL会自动优化为a = 1 and b = 2 and c > 3 and d < 4
,依次命中a、b、c。