[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字句中的字段不该被创建为索引。

~完
转载请注明出处

相关推荐
TDengine (老段)30 分钟前
TDengine 中的视图
数据库·物联网·oracle·时序数据库·tdengine·iotdb
隐-梵1 小时前
Android studio进阶教程之(二)--如何导入高德地图
android·ide·android studio
Kyrie_Li1 小时前
Redis-Sentinel(哨兵模式)
数据库·redis·sentinel
Kika写代码2 小时前
【Android】界面布局-线性布局LinearLayout-例子
android·gitee
wangz762 小时前
kotlin,jetpack compose,使用DataStore保存数据,让程序下次启动时自动获取
android·kotlin·datastore·jetpack compose
计算机毕设定制辅导-无忧学长2 小时前
TDengine 数据写入优化:协议选择与批量操作(一)
网络·数据库·tdengine
Mr.洛 白2 小时前
OpenEuler/CentOS一键部署OpenGauss数据库教程(脚本+视频)
数据库·opengauss·gaussdb·国产数据库安装·安装脚本
炬火初现2 小时前
redis-cpp-cpp如何使用lua脚本
数据库·redis·lua
hxung2 小时前
Redis 数据类型详解
数据库·redis·缓存
因为奋斗超太帅啦3 小时前
MySQL学习笔记(一)——MySQL下载安装配置
笔记·学习·mysql