[MySQL初阶]MySQL(8)索引机制:下

标题:[MySQL初阶]MySQL(8)索引机制:下
@水墨不写bug


文章目录


四、从问题到底层,从现象到本质

1.为什么插入的数据默认排好序

创建如下的一张表:

sql 复制代码
create table if not exists user (
id int primary key,
age int not null,
name varchar(16) not null
);

并且按照如下的顺序插入几条数据:

sql 复制代码
insert into user (id, age, name) values(3, 18, '孙悟空');
insert into user (id, age, name) values(4, 16, '哪吒');
insert into user (id, age, name) values(2, 26, '李小龙');
insert into user (id, age, name) values(5, 36, '成龙');
insert into user (id, age, name) values(1, 56, '奶龙');

插入完成后,查询表中的数据,会发现数据已经被按照id排好序了,这是为什么?
想要理解这个问题,就需要先明白MySQL的Page到底是什么:

2.MySQL的Page

(1)为什么选择用Page?

**IO低效的最主要矛盾不是单次IO的数据量的大小,而是IO的次数。**其次,根据局部性原理,每次IO都加载一个page,相当于预加载的一些数据,如果下次访问的数据刚好已经被加载到内存,这样效率反而可以得到提升。

(2)单个Page的结构(暂时)

单个page的结构如下:
不同的 Page ,在 MySQL 中,都是 16KB ,使用 prev 和 next 构成双向链表。
因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序;如果没有指定主键,那么默认就会按照插入的顺序进行存储。

这就解释了为什么我们插入的数据会按照id进行排序:因为id被设置为主键。

为什么要排序?

为什么要对插入的数据进行排序?本质是为了优化查询效率。

页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询效率是当务之急。

正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程的!

(3)Mysql的多个Page的组织关系(暂时)

(4)页目录的引入与Page结构的修正

类比学校中教材的目录,page内部也引入的类似于目录的机制,当我们想要看某一个知识点,一定是先查书的目录,先大概确定知识点的位置,比如要看指针章节,发现在55-79页是指针相关的知识点,那么下一步就从55页开始查找想看的内容,这样就提高了查询的效率。目录会让整本书的页数增加,所以,目录是一种"以时间换空间"的做法。

另一种做法就是从开始一直向后查找,遍历整本书,这样很低效!

于是,为了提高一个page内部的查询效率,page内部的实际结构如下:
我们要查找id=4记录,之前必须线性遍历4次,才能拿到结果。现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。

于是,MySQL自动排序的原因就是:
便于建立页目录,进而进一步提高查询效率!

(5)多页间的情况

MySQL一个page的大小为16KB,大小是固定的,然而16KB存不下大量的数据,所以必定需要多个页来存储数据:
在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来。然而一般而言,新插入数据会修改page内的多个数据,而不是直接添加在旧数据的后面。
但是这样在Page之间的查询依然是需要遍历的,依然是很低效的!

解决方案是在现有的基础上,给每一个page也带上目录,并且把这些目录统一存放在上一级的page中:
在上一级的page中:

存储下一级目录的索引(这个索引就是每个下一级page的最小索引);

与不同的索引不同的是,上一级page目录管理的是page,而下一级page内部的目录管理的是一行数据。

每一个目录的构成就是:键值+指针

无论是上一级page还是下一级page,本质都是page,只不过:
上一级page(目录页)存储的是下一级page(普通页)的地址;而下一级page(普通页)存储的是用户数据。

当数据进一步增多的时候,我们可以建立第三级别的page:
通过分析,会发现上一级page不需要横向指针,因为上一级的page知识起到了路由的作用,不需要横向遍历。

查询过程分析:

根据上面的这个图示的数据结构我们想要查找主键是3的数据:
过程如下:

从最上层开始第一级目录:1 < 3 < 11 --->从1索引进入下一级page;

第二级目录: 1 < 3 < 6 --->从下标1进入下一级page;

第三级数据页:查找的3一定在这个page中:从数据索引目录2【3】找到3的这一条数据。

整个过程遍历的page的个数就是树的高度!相对于从头到尾遍历所有的page要高效非常多(3这个数据比较小,如果是查找998877这个数据行呢?);


五、B+树的引入与MySQL的InnodeDB下的索引结构

上图的这个结构,其实就是B+树。这也是InnodeDB的索引结构。
为什么InnodeDB选择了B+树,选择B+树的优势?
1.叶子节点保存有数据,非叶子节点没有保存数据,只保存目录项?

这样非叶子节点就可以存储更多的目录项,于是就可以管理更多的叶子page,于是可以推知:这棵树一定是一颗矮胖的树!--->查找数据行时途径的路上节点一定减少--->找到目标数据只需要更少的page,这意味着更少的IO次数---->IO层面提高了搜索的效率。
2.叶节点全都用有横向指针链接起来形成链表结构?

i,这首先是B+的特点;

ii,需要支持范围查找,一个page内的数据不一定够用。


六、InnodeDB为什么不选择其他的数据结构如B树?

链表:线性遍历----->查找非常低效,不合适;
基本的二叉搜索树----->当插入数据接近有序的时候,面临退化为线性结构,仍然会非常低效;
AVL树和红黑树:平衡二叉树---->理论上是可以的,但是二叉树只有两个树杈,分支太少,意味着相同数据量情况下,树的高度增高。与B+树的多叉树结构相比需要查找更多的page,意味着需要更多的IO交互,低效;
哈希表:O(1)直接查找---->在官方的索引实现中,MySQL是支持HASH的,仅仅是InnodeDB和MyISAM不支持。哈希虽然可以O(1)查找,但是在范围查找方面就略显无力了。
B树:B树结构与B+树不同点在于:

B树的非叶子节点既有数据,又有指针;而B+非叶子节点没有数据,只有叶子节点有数据;

B树的叶子节点没有横向指针;B+有。

结构如下:

B树:
B+树:
为什么选择B+?

节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。

叶子节点有横向指针相连,更便于进行范围查找。


七、InnodeDB与MyISAM的索引机制区别

1.存储机制不同:

本文前部分的把数据全都放在叶子节点的存储引擎是InnodeDB的存储引擎;除此之外,MyISAM存储引擎在叶子节点出存储的不是数据,而是数据的地址。

MyISAM存储引擎同样使用B+树,但是在叶子节点键值对数据域存储的是数据行的地址:
聚簇索引与非聚簇索引:
把用户数据和索引数据分离存储的方案叫做非聚簇索引(如MyISAM)。
把用户数据和索引存储在一起的方案叫做聚簇索引(如InnodeDB)。

如何证明?

当我们创建一个引擎为InnodeDB数据表,发现在目录树中出现了两个文件:
.frm:表结构;

.idb:索引和数据;

而当我们创建一个引擎为MyISAM数据表,发现在目录树中出现了三个文件:
.frm:表结构;

.MYD:数据;

.MYI:索引结构;


2.回表查询

MySQL 除了默认会建立主键(我们没有建立主键索引的时候,InnodeDB会默认创建一个索引,来形成B+树,但是由于我们查询的条件不是默认创建的索引,所以按我们的查询条件,只能顺序遍历查找,这样通常效率非常低下)索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做普通索引。

对于MyISAM 而言,创建普通索引和主键索引大体没有区别(创建两个B+树,都是在叶子节点存储数据行的地址),唯一的区别就是主键不能重复,而非主键可以重复

对于InodeDB ,就不一样了:InnodeDB叶子节点存储的是数据本身,而创建普通索引如果还存储数据,那就需要拷贝两份数据,这就会造成空间资源浪费。于是: InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值。
所以InnodeDB通过普通索引找到目标记录需要两遍索引:
首先检索普通索引获取主键;然后通过主键在主键索引中获得数据行,这个过程称为 回表查询


八、索引的分类

索引分为:主键索引、普通索引。
普通索引包括:唯一键索引、可重复的索引。

九、索引的操作

1.主键索引

创建主键索引
i,在建表的时候:

sql 复制代码
create table userA(id int primary key, name varchar(30));
create table userB(id int, name varchar(30), primary key(id));

ii,在建表之后添加:

sql 复制代码
alter table userC add primary key(id);

主键索引的特点:

一个表中只存在一个主键索引;
搜索效率高,因为主键不可重复;
主键索引的列不能为空,不可重复;
主键索引一般都是int。


2.唯一键索引

创建唯一键索引:
i,在建表的时候:

sql 复制代码
create table userD(id int primary key, name varchar(30) unique);
create table userE(id int primary key, name varchar(30), unique(name));

ii,在建表之后添加:

sql 复制代码
alter table userF add unique(name);

唯一键索引的特点:

一张表可以有多个唯一键索引;
查询效率高,不能重复;
添加唯一键索引时,这一列数据不能有重复;
唯一键+not null == 主键索引。


3.可重复索引(普通索引)

创建普通索引:
i,建表的时候

sql 复制代码
create table userG(id int primary key,
	name varchar(20),
	email varchar(30),
	index(name) --在表的定义最后,指定某列为索引
);

ii,创建表之后添加:

sql 复制代码
alter table userH add index(name);

iii,在表的基础上创建:

sql 复制代码
create index idx_name on userG(name);

普通索引的特点:

一张表有多个普通索引;
某一列值有重复,但是需要建立索引,就需要用到普通索引。

4.索引的查询

sql 复制代码
show keys from 表名;
show index from 表名;
desc 表名;

5.索引的删除

sql 复制代码
删除主键索引
alter table 表名 drop primary key;
删除其他索引
alter table 表名 drop index 索引名;
 (索引名就是show keys from 表名中的 Key_name 字段)
drop index 索引名 on 表名;

十、索引创建的原则

  • 频繁作为查询条件的字段应该为索引;
  • 唯一性太差,频繁更新的字段不适合做索引;
  • 不出现在where字句中的字段不该被创建为索引。

~完
转载请注明出处

相关推荐
大熊猫侯佩26 分钟前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩26 分钟前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
EnzoRay27 分钟前
MotionEvent
android
大熊猫侯佩31 分钟前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩34 分钟前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
Ares-Wang38 分钟前
负载均衡LB》》HAproxy
运维·数据库·负载均衡
玲小珑44 分钟前
Auto.js 入门指南(七)定时任务调度
android·前端
AI.NET 极客圈1 小时前
.NET 原生驾驭 AI 新基建实战系列(四):Qdrant ── 实时高效的向量搜索利器
数据库·人工智能·.net
墨狂之逸才1 小时前
adb常用命令调试
android
weixin_470880261 小时前
MySQL体系架构解析(二):MySQL目录与启动配置全解析
数据库·mysql·面试·mysql体系架构·mysql bin目录