二叉搜索树是最为你们所熟知的一种数据结构,就不展开介绍了,它为何不适合用做数据库索引?mysql
(1)当数据量大的时候,树的高度会比较高,数据量大的时候,查询会比较慢;面试
(2)每一个节点只存储一个记录,可能致使一次查询有不少次磁盘IO;sql
IO次数就是树的高度,而“矮胖”就是b树的特征之一, B树属于多叉树又名平衡多路查找树(查找路径不仅两个)数据库
B树,如上图,它的特色是:segmentfault
(1)再也不是二叉搜索,而是m叉搜索;数据结构
(2)叶子节点,非叶子节点,都存储数据;机器学习
(3)中序遍历,能够得到全部节点;学习
画外音,实在不想介绍这个特性:非根节点包含的关键字个数j知足,(┌m/2┐)-1 <= j <= m-1,节点分裂时要知足这个条件。大数据
B树被做为实现索引的数据结构被创造出来,是由于它可以完美的利用“局部性原理”。优化
什么是局部性原理?
局部性原理的逻辑是这样的:
(1)内存读写块,磁盘读写慢,并且慢不少;
(2)磁盘预读:磁盘读写并非按需读取,而是按页预读,一次会读一页的数据,每次加载更多的数据,若是将来要读取的数据就在这一页中,能够避免将来的磁盘IO,提升效率;
画外音:一般,一页数据是4K。
(3)局部性原理:软件设计要尽可能遵循“数据读取集中”与“使用到一个数据,大几率会使用其附近的数据”,这样磁盘预读能充分提升磁盘IO;
B树为什么适合作索引?
(1)因为是m分叉的,高度可以大大下降;
(2)每一个节点能够存储j个记录,若是将节点大小设置为页大小,例如4K,可以充分的利用预读的特性,极大减小磁盘IO;
总结:从平衡二叉树、B树、B+树、B*树整体来看它们的贯彻的思想是相同的,都是采用二分法和数据平衡策略来提高查找数据的速度。
B+树,如上图,还是m叉搜索树,在B树的基础上,作了一些改进:
(1)非叶子节点再也不存储数据,数据只存储在同一层的叶子节点上;
画外音:B+树中根到每个节点的路径长度同样,而B树不是这样。
(2)叶子之间,增长了链表,获取全部节点,再也不须要中序遍历;
这些改进让B+树比B树有更优的特性:
(1)范围查找,定位min与max以后,中间叶子节点,就是结果集,不用中序回溯;
画外音:范围查询在SQL中用得不少,这是B+树比B树最大的优点。
(2)叶子节点存储实际记录行,记录行相对比较紧密的存储,适合大数据量磁盘存储;非叶子节点存储记录的PK,用于查询加速,适合内存存储;
(3)非叶子节点,不存储实际记录,而只存储记录的KEY的话,那么在相同内存的状况下,B+树可以存储更多索引;
最后,量化说下,为何m叉的B+树比二叉搜索树的高度大大大大下降?
大概计算一下:
(1)局部性原理,将一个节点的大小设为一页,一页4K,假设一个KEY有8字节,一个节点能够存储500个KEY,即j=500
(2)m叉树,大概m/2<= j <=m,便可以差很少是1000叉树
(3)那么:
一层树:1个节点,1*500个KEY,大小4K
二层树:1000个节点,1000*500=50W个KEY,大小1000*4K=4M
三层树:1000*1000个节点,1000*1000*500=5亿个KEY,大小1000*1000*4K=4G
画外音:额,帮忙看下有没有算错。
能够看到,存储大量的数据(5亿),并不须要过高树的深度(高度3),索引也不是太占内存(4G)。
总结
数据库索引用于加速查询
虽然哈希索引是O(1),树索引是O(log(n)),但SQL有不少“有序”需求,故数据库使用树型索引
InnoDB不支持哈希索引
数据预读的思路是:磁盘读写并非按需读取,而是按页预读,一次会读一页的数据,每次加载更多的数据,以便将来减小磁盘IO
局部性原理:软件设计要尽可能遵循“数据读取集中”与“使用到一个数据,大几率会使用其附近的数据”,这样磁盘预读能充分提升磁盘IO
数据库的索引最经常使用B+树:
(1)很适合磁盘存储,可以充分利用局部性原理,磁盘预读;
(2)很低的树高度,可以存储大量数据;
(3)索引自己占用的内存很小;
(4)可以很好的支持单点查询,范围查询,有序性查询;
索引文件
与数据文件
是分离
的MyISAM
的索引文件采用B+Tree
索引叶子节点data域
记录的是数据存放的地址
主索引(惟一)
与辅助索引(可重复)
在结构上没有任何区别
数据文件
自己是按照B+Tree
组织的索引结构(主索引:Primary Index
或汇集索引:Clustered Index
),而叶子节点data域
记录的是完整的数据信息,聚簇索引就是数据的存储方式(全部的用户记录都存储在了叶子节点),也就是所谓的索引即数据。
必须有主键
,若是没有显式定义主键
或非NULL的惟一索引
,InnoDB会自动生成6 Bytes的ROWID
做为主键辅助索引
(Secondary Index
)也是按B+Tree
组织,叶子节点data域
记录的是主键值
,所以主键不宜定义太大
辅助索引
须要遍历两遍索引
,首先经过辅助索引
得到主键值,再用主键值在主索引
中获取实际数据InnoDB的主键索引与行记录是存储在一块儿的,故叫作汇集索引(Clustered Index):
没有单独区域存储行记录
主键索引的叶子节点,存储主键,与对应行记录(而不是指针)
咱们举个例子:
CREATE TABLE t ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, key1 INT, common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1) ) Engine=InnoDB CHARSET=utf8;
这个表就包含2个索引(也就是2棵B+树):
以id
列为主键对应的聚簇索引。
为key1
列创建的二级索引idx_key1
。
咱们向表中插入一些记录:
INSERT INTO t VALUES (1, 30, 'b'), (2, 80, 'b'), (3, 23, 'b'), (4, NULL, 'b'), (5, 11, 'b'), (6, 53, 'b'), (7, 63, 'b'), (8, NULL, 'b'), (9, 99, 'b'), (10, 12, 'b'), (11, 66, 'b'), (12, NULL, 'b'), (13, 66, 'b'), (14, 30, 'b'), (15, 11, 'b'), (16, 90, 'b');
因此如今s1
表的聚簇索引示意图就是这样:
s1
表的二级索引示意图就是这样:
从图中能够看出,值为NULL
的二级索引记录都被放到了B+树的最左边,这是由于设计InnoDB的大叔们有规定:
We define the SQL null to be the smallest possible value of a field.
也就是认为NULL
值是最小的。
小贴士:原谅咱们把B+树的结构作了一个如此这般的简化,咱们省略了页面的结构,省略了全部的内节点(只画了了三角形替代),省略了记录之间的链表,由于这些不是本文的重点,画成若是所示的样子只是为了突出叶子节点处的记录是按照给定索引的键值进行排序的。
比方说咱们如今执行下边这个查询语句:
SELECT * FROM t WHERE key1 = 53;
那么语句的执行过程就以下图所示:
用文字描述一下这个过程也就是:
先经过二级索引idx_key1
对应的B+
树快速定位到key1
列值为53
的那条二级索引记录。
而后经过二级索引记录上的主键值,也就是6
到执行回表
操做,也就是到聚簇索引中再找到id
列值为6
的聚簇索引记录。
小贴士:B+树叶子节点中的记录都是按照键值按照从小到大的顺序排好序的,经过B+树索引定位到叶子节点中的一条记录是很是快速的。不过因为咱们并无唠叨内节点、页目录这些东西,因此经过B+树索引定位到叶子节点中的一条记录的过程就不详细唠叨了,这些东西其实都在《MySQL是怎样运行的:从根儿上理解MySQL》的掘金小册里详细讲述过。
像下边这个查询:
SELECT * FROM t WHERE key1 > 20 AND key1 < 50;
它的执行示意图就是这样:
用文字表述就是这样:
先经过二级索引idx_key1
对应的B+
树快速定位到知足key1 > 20
的第一条记录,也就是咱们图中所示的key1
值为23
的那条记录,而后根据该二级索引中的主键值3
执行回表操做,获得完整的用户记录后发送到客户端。
而后根据上一步骤中获取到的key1
列值为23
的二级索引记录的next_record
属性,找到紧邻着的下一条二级索引记录,也就是key1
列值为30
的记录,而后执行回表操做,获得完整用户记录后发送到客户端。
而后再找上一步骤中获取到的key1
列值为30
的二级索引记录的下一条记录,该记录的key1
列值也为30
,继续执行回表操做将完整的用户记录发送到客户端。
而后再找上一步骤中获取到的key1
列值为30
的二级索引记录的下一条记录,该记录的key1
列值为53
,不知足key1 < 50
的条件,因此查询就此终止。
从上边的步骤中也能够看出来:须要扫描的二级索引记录越多,须要执行的回表操做也就越多。若是须要扫描的二级索引记录占所有记录的比例达到某个范围,那优化器就可能选择使用全表扫描的方式执行查询(一个极端的例子就是扫描所有的二级索引记录,那么将对全部的二级索引记录执行回表操做,显然还不如直接全表扫描)。
小贴士:咱们这里仍是定型的分析成本,而不定量分析。定量分析的过程比较复杂,不太小册里有写,有兴趣的同窗能够去看。
因此如今的结论就是:断定某个查询是否可使用索引的条件就是须要扫描的二级索引记录占所有记录的比例是否比较低,较低的话说明成本较低,那就可使用二级索引来执行查询,不然要采用全表扫描。
[1]InnoDB备忘录 - B+Tree索引
http://zhongmingmao.me/2017/05/13/innodb-btree-index/
[2] 平衡二叉树、B树、B+树、B*树 理解其中一种你就都明白了
https://zhuanlan.zhihu.com/p/27700617
[3] Does mysql use B-tree,B+tree or both?
https://dba.stackexchange.com/questions/204561/does-mysql-use-b-tree-btree-or-both
[4] MySQL的索引
[5] 数据库两个神器【索引和锁】
http://www.javashuo.com/article/p-pnqixahw-bu.html
[6] 设计 MySQL 数据表的时候通常都有一列为自增 ID,这样设计缘由是什么,有什么好处?
https://www.zhihu.com/question/28703540/answer/494072901
[7] 机器学习能革了数据库索引的命吗?
https://mp.weixin.qq.com/s/o115JjjtUzJZ4MQ9yO-WuQ
[8] 收藏版MySQL语句加锁分析
[9] 快速理解为啥这个查询使用索引,那个查询不使用索引
https://mp.weixin.qq.com/s/cyr8rW9-iP_N-BWDyqIVEQ
[10] 面试官:为何 MySQL 索引要使用 B+树而不是其它树形结构?好比 B 树?InnoDB一棵B+树能够存放多少行数据?