Mysql存储结构B树与B+树与索引

首先要说明的是,B-树和B树是指同一个结构,并没有所谓的B减树,两种树是B-树和B+树。

Mysql存储结构是一个B+树。

1.存储结构与索引

众所周知,索引是关系型数据库中给数据库表中一列或多列的值排序后的存储结构,它是一种加快查询速度的数据结构,常用索引结构有hash、B-Tree和B+Tree,Mysql选用的是B+树索引。

1)Hash

hash是基于哈希表完成索引存储,哈希表特性是数据存放是散列的。

优点:

等值查询快,通过hash值直接定位到具体的数据。

缺点:

  1. 范围查询效率低(表中的数据是无序数据,在日常开发中通常需要范围查询,该情况下hash需要一个一个查找后合并返回)
  2. hash表在使用的时会将所有数据加载到内存,比较消耗内存
  3. hash算法不好会出现hash碰撞的情况
  4. 哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行
  5. 哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值

2)B-Tree

a)

B树(或B-树、B_树),它是一种m阶平衡多叉树。当m取2时,便是二叉搜索树,其中m指的是一个结点最多有多少个孩子结点。

    对于m阶B树,其具有如下性质:

  1. 根结点至少有两个子女;
  2. 每个结点的值的个数为 1 <= n < m;
  3. 所有的叶子结点都位于同一层;
  4. 除根结点以外的所有结点(不包括叶子结点)的孩子正好是值个数的加1;
  5. 每个结点中的值都按照从小到大的顺序排列,每个值的左子树中的所有的值都小于它,而右子树中的所有的值都大于它。

如下图所示:

上图为3阶B树,在实际应用中的B树的阶数m都非常大(通常大于100),所以即使存储大量的数据,B树的高度仍然比较小,这有利于树的插入删除。在数据库中我们将B树(和B+树)作为索引结构,可以加快查询速速。

  B树的插入

 如果插入的结点只有一个数值,直接在该结点插入即可。例如,在上图中插入9,则直接在10结点前面插入9即可。但如果插入44,此时便需要通过结点的向上分裂来完成插入。

     插入44:

                                   

    发现此结点有3个值,不满足3阶B树,因此要进行分裂,将中间的40向上结点移动:

                                   

    分裂后此B树变成了4阶B树,不满足3阶B树条件,原因是40移动到上结点所致,因此继续向上结点移动,将50移动到上节点:

                                      

     此时发现又出现3个值的结点,继续进行分裂:

                                      

      此时便满足条件,完成。

B树的删除按照插入的方法反过来操作即可,即父结点(如果不符合父结点大于左结点小于右结点的条件,则与上层父节点位置调换,直到符合条件为止)不断下移合并,直到符合条件为止。

b)

B树在磁盘存储中:

B-Tree特点:

  1. 所有键值数据分布在整棵树各个节点中
  2. 搜索有可能在非节点结束,在关键字全集内查找,类似二分查找
  3. 所有叶子节点都在同一层,并且以升序排列

3)B+Tree

a)

在B+树中,只有叶子节点存储数据,其它中间结点全部是索引。在数据库的聚集索引中,叶子节点直接包含数据库中某一行数据。在非聚集索引中,叶子节点带有指向数据库行的指针。

B+树是B树的一种变体,有着比B树更高的查询性能,B+树和B树除了有一些共同特点外,还有一些新的特点:

  1. 有k个子树的中间结点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
  2. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
  3. 所有的中间结点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

   下面使用数值来表示一棵B+树:

可以看出,B+树的每个结点的最大或最小元素都出现在下一个结点的首或尾

B+树的查找

     B+树的查找有两种方式:从最小值进行顺序查找;从根结点开始,进行随机查找。在查找时,若非终端结点上的关键值等于给定值,并不终止,而是继续向下直到叶子结点(因为叶子结点才存数据)。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。其余同B-树的查找类似。

     由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引,而B树则常用于文件索引。

B+树的插入

                                            

假设我们要向上图插入0,发现没有破坏B+树结构,直接在1,2结点处插入即可。

如果在结点的中间插入并破坏了B+树的结构:

但是如果我们要插入12,则发现破坏了B+树的结构,则:

                                              

分裂破坏了结构的结点,并将12移到上结点:

                                                 

插入完毕。

如果在端点处插入并破坏了B+树的结构:

假如插入16:

                                               

分裂后,父结点要配合子结点的端点值:

                                            

删除操作,只需将插入操作进行反向操作即可。读者可以想想如何删除16。

B+数的优势:

  1. 单一节点存储更多的元素,使得查询的IO次数更少。(应用于文件系统、数据库系统)
  2. 所有查询都要查找到叶子节点,查询性能稳定。
  3. 所有叶子节点形成有序链表,便于范围查询。

b)

Mysql存储中

B+Tree 是在B-Tree的基础之上做的一种优化,变化如下:

  1. B+Tree 非叶子节点不存放数据
  2. 叶子节点存储关键字和数据,非叶子节点的关键字也会沉到叶子节点,并且排序
  3. 叶子节点两两指针相互连接,形成一个双向环形链表(符合磁盘的预读特性),顺序查询性能更高

Mysql为什么选择B+Tree

Mysql官网文档中写到InnoDB索引用的是 B-tree,但是底层用的是B+Tree。Mysql存储数据是以页为单位,默认一个页可以存放16K数据。假设B-Tree和B+Tree都是3层深度,表中每个记录为1K(假设的,一般不会这么大,别较真),那么三层深度的B-Tree存储 16 x 16 x 16 = 4096(比这个数还要少,因为每个页中还要存放指针和其它的数据)。B+Tree第一、二层存放的是key,假设是Long类型的主键,那么第一、二层每页存放数据约为 16 x 1024 / 8 = 2048,三层深度可以存放 2048 x 2048 x 16 = 6700W。MySQL查询过程是按页加载数据的,每加载一页就是一次IO操作,B+Tree进行三次IO可以查询6700W数据量。从这里也可以知道Mysql一般设置三层深度就足够了。

2.聚集索引与非聚集索引

聚集(clustered)索引,也叫聚簇索引

定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。

打个比方,把数据表比作新华字典,聚集索引就是拼音目录,而每个字存放的页码就是实际的数据物理地址,如果要查询一个“草”字,我们只需要查询“草”字对应在新华字典拼音目录对应的页码,就可以查询到对应的“草”字所在的位置,而拼音目录对应的A-Z的字顺序,和新华字典实际存储的字的顺序A-Z也是一样的,如果我们中文新出了一个字,拼音开头第一个是B,那么他插入的时候也要按照拼音目录顺序插入到A字的后面。

如下表所示:

第一列的地址表示该行数据在磁盘中的物理地址,后面三列表示数据库表的真实数据,其中id是主键,建立了聚集索引

数据行的物理顺序与列值的顺序相同,如果我们查询id比较靠后的数据,那么这行数据的地址在磁盘中的物理地址也会比较靠后。而且由于物理排列方式与聚集索引的顺序相同,所以只能建立一个聚集索引。

                                                    聚集索引实际存放的示意图

从上图可以看出聚集索引的好处了,索引的叶子节点就是对应的数据节点(MySQL的MyISAM除外,此存储引擎的聚集索引和非聚集索引只多了个唯一约束,其他没什么区别),可以直接获取到对应的全部列的数据,而非聚集索引在索引没有覆盖到对应的列的时候需要进行二次查询。因此在查询方面,聚集索引的速度往往会更占优势

非聚集索引

定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。

按照上述比喻,非聚集索引就像新华字典的偏旁字典,存放的结构顺序与实际存放顺序不一定一致。

                                          非聚集索引实际存放的示意图

非聚集索引的二次查询问题

非聚集索引叶节点仍然是索引节点,只是有一个指针指向对应的数据块,此如果使用非聚集索引查询,而查询列中包含了其他该索引没有覆盖的列,那么他还要进行第二次的查询,查询节点上对应的数据行的数据。

如有以下表t1

以及聚集索引clustered index(id), 非聚集索引index(username)。

使用以下语句进行查询,不需要进行二次查询,直接就可以从非聚集索引的节点里面就可以获取到查询列的数据。

select id, username from t1 where username = '小明'
select username from t1 where username = '小明'

但是使用以下语句进行查询,就需要二次的查询去获取原数据行的score:

select username, score from t1 where username = '小明'

如何解决非聚集索引的二次查询(回表查询)问题

复合索引(覆盖索引):将被查询的字段,建立到联合索引里去。

建立两列以上的索引,即可查询复合索引里的列的数据而不需要进行回表二次查询,如index(col1, col2),执行下面的语句

select col1, col2 from t1 where col1 = '213';

要注意使用复合索引需要满足最左侧索引的原则,也就是查询的时候如果where条件里面没有最左边的一到多列,索引就不会起作用。

总结:

  1. 使用聚集索引的查询效率要比非聚集索引的效率要高,但是如果需要频繁去改变聚集索引的值,写入性能并不高,因为需要移动对应数据的物理位置。
  2. 非聚集索引在查询的时候可以的话就避免二次查询,这样性能会大幅提升。
  3. 不是所有的表都适合建立索引,只有数据量大表才适合建立索引,且建立在选择性高的列上面性能会更好。

下面举一个具体的例子

 

InnoDB聚集索引和普通索引有什么差异?

InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:

(1)如果表定义了PK,则PK就是聚集索引;

(2)如果表没有定义PK,则第一个not NULL unique列是聚集索引;

(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;

画外音:所以PK查询非常快,直接定位行记录。

InnoDB普通索引的叶子节点存储主键值。

 画外音:注意,不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针。

举个栗子,不妨设有表:

  t(id PK, name KEY, sex, flag);

画外音:id是聚集索引,name是普通索引。

 

表中有四条记录:

  1, shenjian, m, A

  3, zhangsan, m, A

  5, lisi, m, A

  9, wangwu, f, B

两个B+树索引分别如上图:

  (1)id为PK,聚集索引,叶子节点存储行记录;

  (2)name为KEY,普通索引,叶子节点存储PK值,即id;

 

既然从普通索引无法直接定位行记录,那普通索引的查询过程是怎么样的呢?

通常情况下,需要扫码两遍索引树。

 

例如:

1

select from where name='lisi'; 

是如何执行的呢?

粉红色路径,需要扫码两遍索引树:

(1)先通过普通索引定位到主键值id=5;

(2)在通过聚集索引定位到行记录;

 

这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。

提高效率,索引覆盖!如何实现索引覆盖:常见的方法是:将被查询的字段,建立到联合索引里去。

仍是之前中的例子:

1

2

3

4

5

6

create table user (

    id int primary key,

    name varchar(20),

    sex varchar(5),

    index(name)

)engine=innodb;

1. 第一个sql 

1

select id,name from user where name='shenjian'; 

能够命中name索引,索引叶子节点存储了主键id,通过name的索引树即可获取id和name,无需回表,符合索引覆盖,效率较高。

2. 第二个sql 

1

select id,name,sex from user where name='shenjian';

能够命中name索引,索引叶子节点存储了主键id,但sex字段必须回表查询才能获取到,不符合索引覆盖,需要再次通过id值扫码聚集索引获取sex字段,效率会降低。

如果把(name)单列索引升级为联合索引(name, sex)就不同了。

1

2

3

4

5

6

create table user (

    id int primary key,

    name varchar(20),

    sex varchar(5),

    index(name, sex)

)engine=innodb;

可以看到:

1

2

3

select id,name ... where name='shenjian';

 

select id,name,sex ... where name='shenjian';

都能够命中索引覆盖,无需回表。

 

索引覆盖优化SQL场景

场景1:全表count查询优化 

关于这点的解释,可以参考这里,count(*)的查询:

  1. 当没有非主键索引时,会使用主键索引
  2. 如果存在非主键索引的话,会使用非主键索引
  3. 如果存在多个非主键索引,会使用一个最小的非主键索引

其原因是:在innodb中,非主键索引叶子节点存储的结构是:索引+主键;主键索引叶子节点是:主键+表数据。在1个page里面,非主键索引可以存储更多的条目,例如:
对于一张表,如果有1000000数据,使用非主键索引扫描的page数可能是100 ,而使用主键索引page数可能是500,此时使用非主键索引的性能会更好。同理如果存在多个非主键索引,会使用一个最小的非主键索引,也是为了在一个page里存储更多的数据,从而减少扫描次数,提高性能。

这里使用的是单字段count()查询,意思差不多。

原表为:

user(PK id, name, sex);

直接:

1

select count(namefrom user;

不能利用索引覆盖。

添加索引:

1

alter table user add key(name);

就能够利用索引覆盖提效。

场景2:列查询回表优化

1

select id,name,sex ... where name='shenjian';

这个例子不再赘述,将单列索引(name)升级为联合索引(name, sex),即可避免回表。

场景3:分页查询

1

select id,name,sex ... order by name limit 500,100;

将单列索引(name)升级为联合索引(name, sex),也可以避免回表。

最后这一段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/418316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习(2006)vue之电商管理系统电商系统之绘制商品参数的复选框

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

酒厂选址(codevs 1507)

题目描述 Description Abstinence&#xff08;戒酒&#xff09;岛的居民们酷爱一种无酒精啤酒。以前这种啤酒都是从波兰进口&#xff0c;但今年居民们想建一个自己的啤酒厂。岛上所有的城市都坐落在海边&#xff0c;并且由一条沿海岸线的环岛高速路连接。酒厂的投资者收集了关于…

前端学习(2007)vue之电商管理系统电商系统之优化复选框的样式

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

MapReduce改造fp-growth算法

1. FP-Growth算法弊端 FP-Growth算法是挖掘频繁项集最常用的算法之一&#xff0c;其是基于迭代FP-Tree生成频繁项集的关联规则算法。此算法仅进行两次数据集扫描&#xff0c;递归迭代构建FP-Tree(FP条件树)&#xff0c;当FP-Tree中只有一个单分支时&#xff0c;递归迭代构建结…

《一线架构师实践指南》—— 读后总结

之前总觉得架构是一件很高大上的工作&#xff0c;跟普通的编码设计不太一样。前一段实践&#xff0c;自己也尝试做过架构的工作&#xff0c;可惜经验不足导致架构非常混乱。这里读完这本书&#xff0c;大体上对架构的工作有所了解&#xff0c;也稍微摸清了些门道。 我理解的架构…

随机排列实现 -shuffle算法

随机排列实现 -shuffle算法 洗牌算法&#xff08;shuffle&#xff09;&#xff1a;原理是&#xff0c;一个数A&#xff0c;随机选一个在这个数A之前的下标&#xff0c;将这个下标对应数字与A对换&#xff1b;之后不再考虑最后的这一个数&#xff1b; 类似于无放回的抽签问题&…

前端学习(2008)vue之电商管理系统电商系统之获取静态属性列表

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

转载_递归树求递归算法的时间复杂度

递归算法时间复杂度的计算方程式一个递归方程&#xff1a; 在引入递归树之前可以考虑一个例子&#xff1a; T(n) 2T(n/2) n2 迭代2次可以得&#xff1a; T(n) n2 2(2T(n/4) (n/2) 2) 还可以继续迭代&#xff0c;将其完全展开可得&#xff1a; T(n) n2 2((n/2) 2 2((n/2…

前端学习(2011)vue之电商管理系统电商系统之初步使用upload上传组件

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

为什么使用Deque而不使用Stack构造栈

为什么使用Deque而不使用Stack构造栈 Class Stack<E> java.lang.Object java.util.AbstractCollection<E> java.util.AbstractList<E> java.util.Vector<E> java.util.Stack<E> 实现的所有接口 Serializable &#xff0c; Cloneable &#xff…

前端学习(2012)vue之电商管理系统电商系统之手动为upload组件添加请求头

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

计算器、中文转数字

计算器 1.字符串转整数 string s "458";int n 0; for (int i 0; i < s.size(); i) {char c s[i];n 10 * n (c - 0); } // n 现在就等于 458 2.处理加减法 我们拿字符串算式1-123为例&#xff0c;来说一个很简单的思路&#xff1a; 1、先给第一个数字加…

前端学习(2009)vue之电商管理系统电商系统之渲染商品属性的结构

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

浏览器上请求URL的全部过程

分为六步 1. 域名解析   浏览器首先要做去获得想访问网页的IP地址&#xff0c;浏览器会发送一个UDP的包给DNS域名解析服务器。DNS就会返回给我们百度的IP地址&#xff0c;通常来说浏览器会把访问过的网站的IP保存下来&#xff0c;这样下次访问的时候就会快很多&#xff0c;能…

前端学习(2013)vue之电商管理系统电商系统之监听on-success事件

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

常见算法复习整理1

数据结构笔记 1.迭代与递归 递归过程中的递归因子本身可以被忽略&#xff08;被计入它自己的过程中了&#xff09; 递归跟踪、递推方程。递归基 减而治之&#xff1a;Decrease and Conquer 线性递归的模式 T(n) T(n-1) O(1) 分而治之&#xff1a;Divide and Conquer 一般…

前端学习(2014)vue之电商管理系统电商系统之监听on-remove事件

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

HIT计网笔记

一、实践课笔记 1. 路由器连接的是两个不同的网络&#xff0c;交换机连接的是同一个网络。路由器负责在不同网段转移数据&#xff0c;交换机看mac地址&#xff0c;路由器看ip地址。 2. 子网掩码可以分离出IP地址中的网络地址和主机地址&#xff0c;同一个网络的网络地址相同&…

前端学习(2015)vue之电商管理系统电商系统之实现图片的预览效果

目录结构 router.js import Vue from vue import Router from vue-router import Login from ./components/Login.vue import Home from ./components/Home.vue import Welcome from ./components/Welcome.vue import Users from ./components/user/Users.vue import Right fr…

几种排序与最大K问题

图片链接 0. 链表归并和快排 链表排序常用归并&#xff0c;但是快排面试常出&#xff1b; 一定要理解归并的本质&#xff1a; 两步走&#xff1a; 1. 先切分成(有序)两部分&#xff0c;此处各部分都是有序的 2. 两部分有序合并 切分成两部分的最小粒度是一个元素&#…