我们已经知道在单一数据页中查找数据时, 如果查找条件是主键的话, 可以使用二分法定位槽, 然后顺序遍历槽中的数据查找指定数据. 但是我们并不知道如何在数以万计的页中定位数据在哪个页中, 在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据我们刚刚唠叨过的查找方式去查找指定的记录。

简单索引介绍

为了能够快速定位数据在哪个页中, 索引规定, 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。 下面使用一个案例来看索引如何提升查找数据的效率的:

创建一个表:

mysql> CREATE TABLE index_demo(

-> c1 INT,

-> c2 INT,

-> c3 CHAR(1),

-> PRIMARY KEY(c1)

-> ) ROW_FORMAT = Compact;

Query OK, 0 rows affected (0.03 sec)

插入三条数据:

mysql> INSERT INTO index_demo VALUES(1, 4, 'u'), (3, 9, 'd'), (5, 3, 'y');

Query OK, 3 rows affected (0.01 sec)

Records: 3 Duplicates: 0 Warnings: 0

假设一个页里面只能存放三条普通 user records. index_demo 表中的 3 条记录都被插入到了编号为 10 的数据页中了, 再来插入一条记录:

mysql> INSERT INTO index_demo VALUES(4, 4, 'a');

Query OK, 1 row affected (0.00 sec)

因为页 10 最多只能放 3 条记录,所以我们不得不再分配一个新页:

页 10 中用户记录最大的主键值是 5,而页 28 中有一条记录的主键值是 4,因为 5 > 4,所以这就不符合下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值的要求,所以在插入主键值为 4 的记录的时候需要伴随着一次记录移动,也就是把主键值为 5 的记录移动到页 28 中,然后再把主键值为 4 的记录插入到页 10 中:

这个过程表明了在对页中的记录进行增删改操作的过程中,我们必须通过一些诸如记录移动的操作来始终保证这个状态一直成立:下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。这个过程我们也可以称为页分裂。

在多次插入数据后, 此时变成了:

所以如果想从这么多页中根据主键值快速定位某些记录所在的页,我们需要给它们做个目录,每个页对应一个目录项,每个目录项包括下边两个部分:

页的用户记录中最小的主键值,我们用key来表示。

页号,我们用page_no表示。

我们只需要把几个目录项在物理存储器上连续存储,比如把他们放到一个数组里,就可以实现根据主键值快速查找某条记录的功能了。比方说我们想找主键值为20的记录,具体查找过程分两步:

先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12 < 20 < 209),它对应的页是页9。

再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

这个页目录的名字起名为索引.

InnoDB 索引

上边之所以称为一个简易的索引方案,是因为我们为了在根据主键值进行查找时使用二分法快速定位具体的目录项而假设所有目录项都可以在物理存储器上连续存储

InnoDB是使用页来作为管理存储空间的基本单位,也就是最多能保证16KB的连续存储空间,而随着表中记录数量的增多,需要非常大的连续的存储空间才能把所有的目录项都放下, 而且, 我们时常会对记录进行增删,假设我们把页28中的记录都删除了,页28也就没有存在的必要了,那意味着目录项2也就没有存在的必要了,这就需要把目录项2后的目录项都向前移动一下

此时需要 innodb 提供一个非常灵活地方式去管理目录项记录, 那是不是可以将目录项记录按照普通数据项记录的管理方式进行管理. 只不过数据的 record_type 会区别数据是目录项记录还是普通数据记录. 经过重新构思后, 数据项记录也放入页中, 按照页管理数据的方式进行管理:

目录项 record 和普通数据 record 的区别:

1.目录项记录的record_type值是1,而普通用户记录的record_type值是0。

2.目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列,另外还有InnoDB自己添加的隐藏列。

3.只有在存储目录项记录的页中的主键值最小的目录项记录的min_rec_mask值为1,其他别的记录的min_rec_mask值都是0

除了上述三点不同, 其他的部分均相同. 那么也就是说当存储索引的页装满了以后可以扩展一个页继续存储, 并且生成一个更高级的页目录, 最后效果图就成了这样:

以及慢慢的抽象化:

它的名称是 B+ 树.

不论是存放用户记录的数据页,还是存放目录项记录的数据页,我们都把它们存放到 B+ 树这个数据结构中了,所以我们也称这些数据页为节点。从图中可以看出来,我们的实际用户记录其实都存放在 B+ 树的最底层的节点上,这些节点也被称为叶子节点或叶节点,其余用来存放目录项的节点称为非叶子节点或者内节点,其中 B+ 树最上边的那个节点也称为根节点. 为了讨论方便, 规定最下边的那层,也就是存放我们用户记录的那层为第 0 层,之后依次往上加.

粗略的估算一下树的威力. 如果一个页能存放 100 条数据, 那么:

如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放100条记录

如果B+树有2层,最多能存放1000×100=100000条记录

如果B+树有3层,最多能存放1000×1000×100=100000000条记录。

如果B+树有4层,最多能存放1000×1000×1000×100=100000000000条记录

一般表中都不会有 100000000000 记录, 所以一般情况下,我们用到的 B+ 树都不会超过4层. 通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的Page Directory(页目录),所以在页面内也可以通过二分法实现快速定位记录

聚簇索引

上面的树的结构有以下两个特点:

1.使用记录主键值的大小进行记录和页的排序

2.B+树的叶子节点存储的是完整的用户记录

具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX语句去创建, InnoDB存储引擎会自动的为我们创建聚簇索引。

由于聚簇索引是通过主键去查数据的, 当我们的查询条件无法包含主键时, 难道就得从头到尾遍历所有的数据了吗?

二级索引

假设我们的查询条件是 c2 列的值, 那么我们就应该创建一个新的以 c2 列的值进行排序后的 b+ 树.

这个B+树与上边介绍的聚簇索引有几处不同:

使用记录c2列的大小进行记录和页的排序,这包括三个方面的含义:

页内的记录是按照c2列的大小顺序排成一个单向链表。

各个存放用户记录的页也是根据页中记录的c2列大小顺序排成一个双向链表。

存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表。

B+树的叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两个列的值。

目录项记录中不再是主键+页号的搭配,而变成了c2列+页号的搭配。

所以如果我们现在想通过c2列的值查找某些记录的话就可以使用我们刚刚建好的这个B+树了。以查找c2列的值为4的记录为例,查找过程如下:

确定目录项记录页, 根据根页面,也就是页44,可以快速定位到目录项记录所在的页为页42(因为2 < 4 < 9)。

通过目录项记录页确定用户记录真实所在的页。在页42中可以快速定位到实际存储用户记录的页,但是由于c2列并没有唯一性约束,所以c2列值为4的记录可能分布在多个数据页中,又因为2 < 4 ≤ 4,所以确定实际存储用户记录的页在页34和页35中。

在真实存储用户记录的页中定位到具体的记录。到页34和页35中定位到具体的记录。

但是这个B+树的叶子节点中的记录只存储了c2和c1(也就是主键)两个列,所以我们必须再根据主键值去聚簇索引中再查找一遍完整的用户记录。这个过程也被称为回表。也就是根据c2列的值查询一条完整的用户记录需要使用到2棵B+树, 之所以需要回表操作是因为节省空间.

因为这种按照非主键列建立的B+树需要一次回表操作才可以定位到完整的用户记录,所以这种B+树也被称为二级索引(英文名secondary index),或者辅助索引。由于我们使用的是c2列的大小作为B+树的排序规则,所以我们也称这个B+树为为c2列建立的索引。

联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照c2和c3列的大小进行排序,这个包含两层含义:

先把各个记录和页按照c2列进行排序。

在记录的c2列相同的情况下,采用c3列进行排序

效果图:

每条目录项记录都由c2、c3、页号这三个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序。

B+树叶子节点处的用户记录由c2、c3和主键c1列组成

以c2和c3列的大小为排序规则建立的B+树称为联合索引,本质上也是一个二级索引。它的意思与分别为c2和c3列分别建立索引的表述是不同的

InnoDB的B+树索引的注意事项

根页的页码是不会改变的

当我们为一个表创建聚簇索引时(不创建的话也会默认有一个), 就已经有根页了, 只是此时根页里面没有存储任何数据.

当为表中添加数据后, 根页先存储数据, 当数据存储满了以后, 会将数据复制出来放入页 a, 再加入数据时, 产生页分裂, 出现页 b, 此时根页里面存储的则是页目录记录.

凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。

页中节点的唯一性

这是针对二级索引才有的问题, 当二级索引按照某列进行排序后, 可能会出现相同的情况, 此时需要加上主键用以区分唯一性. 再添加数据才会定位到该添加的页.

一个页面最少存储2条记录

这是由于如果只放一条记录的话, 画面太美不敢想象.

MySQL中创建和删除索引的语句

InnoDB 会自动为主键或者声明为 UNIQUE 的列去自动建立 B+ 树索引,但是如果我们想为其他的列建立索引就需要我们显式的去指明。

我们可以在创建表的时候指定需要建立索引的单个列或者建立联合索引的多个列:

CREATE TALBE 表名 (

各种列的信息 ··· ,

[KEY|INDEX] 索引名 (需要被索引的单个列或多个列)

)

或者对表修改:

ALTER TABLE 表名 ADD [INDEX|KEY] 索引名 (需要被索引的单个列或多个列);

也可以在修改表结构的时候删除索引:

ALTER TABLE 表名 DROP [INDEX|KEY] 索引名;

具体例子:

CREATE TABLE index_demo(

c1 INT,

c2 INT,

c3 CHAR(1),

PRIMARY KEY(c1),

INDEX idx_c2_c3 (c2, c3)

);

ALTER TABLE index_demo DROP INDEX idx_c2_c3;

mysql s索引 树_mysql 学习 - B+树索引相关推荐

  1. mysql 查找相似数据_MySQL学习笔记之索引

    索引是存储引擎用于快速找到记录的一种数据结构. 索引对于良好的性能非常关键.尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要.在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但 ...

  2. mysql为什么不用b树_MySQL用B+树(而不是B树)做索引的原因

    众所周知,MySQL的索引使用了B+树的数据结构.那么为什么不用B树呢? 先看一下B树和B+树的区别. 1.B树 维基百科对B树的定义为"在计算机科学中,B树(B-tree)是一种树状数据结 ...

  3. mysql原生建立索引_MySQL学习笔记之索引

    索引是存储引擎用于快速找到记录的一种数据结构. 索引对于良好的性能非常关键.尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要.在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但 ...

  4. mysql innodb 索引结构_Mysql 学习笔记:InnoDB 索引结构浅析

    索引是检索图书资料的一种工具,把书刊中的内容或项目分类摘录,注明页数,按一定次序排列. 针对不同的数据存储结构有不同的数据查找方式. 1. 数据结构 1.1 B树 B树又名平衡多路查找树,主要用于文件 ...

  5. mysql 查看索引深度_mysql 学习 - 索引深度理解

    使用索引的代价 在熟悉了B+树索引原理之后,本篇文章的主题是唠叨如何更好的使用索引,虽然索引是个好东西,可不能乱建,在介绍如何更好的使用索引之前先要了解一下使用这玩意儿的代价,它在空间和时间上都会拖后 ...

  6. mysql模糊查询索引失效_MySql学习笔记(九):索引失效

    数据准备:CREATE TABLE `t_blog` ( `id` int(11) NOT NULL auto_increment, `title` varchar(50) default NULL, ...

  7. MySQL索引的理解学习,面试不问索引原理就是事务原理

    目录 MySQL执行SQL的整体流程 引言, MySQL索引底层学习原因 磁盘介绍(理解磁盘IO) 索引底层数据结构B+树 B+树(聚集索引) B+树(辅助索引) 思考一下为何使用B+树结构, 不是B ...

  8. mysql传小马_Mysql 学习笔记

    1.关系型数据库 关系:由行和列组成的二维表 表:至少要有列,可以没有行. 列:是实体的属性. 数据模型:层次模型.网状模型.关系模型.非关系模型. DBMS:DataBase Mangenent S ...

  9. mysql防止索引崩溃_MySQL优化之避免索引失效的方法

    在上一篇文章中,通过分析执行计划的字段说明,大体说了一下索引优化过程中的一些注意点,那么如何才能避免索引失效呢?本篇文章将来讨论这个问题. 避免索引失效的常见方法 1.对于复合索引的使用,应按照索引建 ...

最新文章

  1. Spring - Java/J2EE Application Framework 应用框架
  2. Deeplearning入门篇(2)
  3. linux发挥显卡性能,Linux Kernel 2.6.30下Intel显卡性能有大幅提升!
  4. Gartner 如何看 RASP 和 WAF?
  5. 凯撒密码中有数字怎么办_古典密码
  6. 关于Connection的一个测试
  7. 苹果id无法登陆_教你在iPhone上如何注册 ID帐户,并注意使用事项
  8. 校园虚拟服务器设备配置规划,校园高性能虚拟化服务器平台建设规划.doc
  9. ubuntu上安装使用冰封王座
  10. 程序员眼中的中国传统文化_王阳明《传习录》2
  11. node创建ETH地址及导出私钥
  12. 什么是前端模块化?前端模块化开发到底有无必要
  13. 基于linux的mplay的mp3程序,基于Linux下的开源wavplay播放器
  14. 3种方法教你应对高智商型反社会人格者
  15. 容器学习点点滴滴(三)
  16. Spring Getting Started Guides migrated to Asciidoctor
  17. 机器学习入门1-译文-机器学习是什么以及它的重要性(machine learning--what it is and why it matters)
  18. 爬取github上热门项目并绘制图表
  19. XX健康:移动端开发-体检预约验证码30秒倒计时短信验证码获取与验证DatePicker日历展示提交预约复杂流程阿里短信工具类
  20. 【大厂面试】面试官看了赞不绝口的Redis笔记

热门文章

  1. java swagger ui 教程_java集成Swagger的步骤详解
  2. java 关闭中断_Raspberry pi使用中断方法关闭(关闭时出现垃圾代码)
  3. github上成员贡献量_Bifrost 长期贡献规则,获得 BNC 的 5 种方式
  4. linux中文快捷键,Linux系统快捷键最全合集
  5. TensorFlow:字词的向量表示
  6. html如何在屏幕中显示加载,在HTML5应用中加载屏幕
  7. Excel控件 Spire.XLS系列教程(2):C# 设置现有 Excel 图表的数据标签样式
  8. HYSBZ 2145 悄悄话
  9. Linux分区和加密分区操作
  10. 20非常有用的Java程序片段(1)