标题:[MySQL初阶]MySQL(8)索引机制:下
@水墨不写bug
文章目录
- 四、从问题到底层,从现象到本质
- 五、B+树的引入与MySQL的InnodeDB下的索引结构
- 六、InnodeDB为什么不选择其他的数据结构如B树?
- 七、InnodeDB与MyISAM的索引机制区别
- 八、索引的分类
- 九、索引的操作
- 十、索引创建的原则
四、从问题到底层,从现象到本质
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字句中的字段不该被创建为索引。
~完
转载请注明出处