当前位置: 首页 > article >正文

[MySQL#10] 索引底层(1) | Page | 页目录

 目录

1. 初识索引

2. 认识磁盘

3. MySQL与磁盘交互基本单位

4. 索引的理解

1. 重谈Page

2. 为什么IO交互要用Page

3. 有主键的表插入数据时的排序

4. 单个Page与多个Page

4.1 单个Page

4.2 多个Page

目录

单Page目录

多Page目录


在看本文之前,可以回顾一下这两篇前文~

[MySQL#5] 表约束(2) | 唯一键 | 外键 | 简易商店数据库

【Linux】(26) 详解磁盘与文件系统:从物理结构到inode机制

1. 初识索引

  • 索引是提高数据库性能的关键工具。
  • 它通过改变数据的组织方式,显著提升查询速度,而无需增加硬件资源或修改应用程序代码。
  • 然而,索引的使用也并非没有代价。
  • 查询速度的提升是以插入、更新和删除操作的性能下降为代价的,因为这些写操作会增加大量的I/O操作。
  • 因此,索引的价值在于提高海量数据的检索速度。

MySQL的CURD操作与内存

  • 所有MySQL的CURD操作都在内存中进行。MySQL在启动时会预先开辟一大块内存空间,用于缓存数据。
  • 这些数据会在适当的时候被刷新到磁盘中进行持久化
  • 因此,MySQL服务器本质上是在内存中运行的,所有的数据库操作都在内存中进行。索引同样也是在内存中的一种特定结构
  • 索引是提高效率的,一般我们知道提高算法效率的因素:1. 组织数据的方式 ,2. 算法本身
  • 索引是更改特定组织数据的方式,把以前数据的组织方式以新的数据结构组织起来。所以索引是内存中一种特定组织的一种数据结构,具体是什么结构后面再说,

索引的类型

常见的索引类型包括:

  • 主键索引(Primary Key)
  • 唯一索引(Unique)
  • 普通索引(Index)
  • 全文索引(Fulltext)——主要用于解决中文索引问题

创建海量数据表

为了演示索引的效果,我们先创建一个包含800万条记录的表,并观察没有索引时的查询性能。

-- 产生随机字符串
delimiter $$
create function rand_string(n INT)
returns varchar(255)
begin
    declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    declare return_str varchar(255) default '';
    declare i int default 0;
    while i < n do
        set return_str = concat(return_str, substring(chars_str, floor(1 + rand() * 52), 1));
        set i = i + 1;
    end while;
    return return_str;
end $$
delimiter ;

-- 产生随机数字
delimiter $$
create function rand_num()
returns int(5)
begin
    declare i int default 0;
    set i = floor(10 + rand() * 500);
    return i;
end $$
delimiter ;

-- 创建存储过程,向雇员表添加海量数据
delimiter $$
create procedure insert_emp(in start int(10), in max_num int(10))
begin
    declare i int default 0;
    set autocommit = 0;
    repeat
        set i = i + 1;
        insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)
        values (start + i, rand_string(6), 'SALESMAN', 0001, curdate(), 2000, 400, rand_num());
    until i = max_num
    end repeat;
    commit;
end $$
delimiter ;

-- 执行存储过程,添加8000000条记录
call insert_emp(100001, 8000000);

注意:创建表单前要注意,恢复默认结束符:使用 delimiter ; 将结束符恢复为默认的分号 ;

查询性能测试

查询员工编号为998877的员工:

select * from EMP where empno = 998877;

没有索引时,查询非常慢,耗时约4.29秒。在实际项目中,如果放在公网中,同时有1000人并发查询,可能会导致系统崩溃。

解决方法:创建索引

alter table EMP add index(empno);

创建索引后,查询速度大幅提升。


2. 认识磁盘

硬件理解

MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。

磁盘是计算机中的一个机械设备,相比于计算机其他电子元件,磁盘效率是比较低的,在加上IO本身的特征,可以知道,如何提升效率,是 MySQL 的一个重要话题。

磁盘结构

  • 扇区:数据库文件保存在磁盘的扇区中。每个扇区通常是512字节。
  • 柱面(Cylinder):同半径的磁道构成一个柱面。
  • 磁头(Heads):每个盘面有一个磁头,磁头和盘面的对应关系是1对1的。
  • CHS(Cylinder, Head, Sector):通过磁头、柱面和扇区编号定位扇区。
  • LBA(Logical Block Addressing):系统使用线性地址,最终转换为CHS。

再细看磁盘中一个盘片

扇区

数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格中,就是我们经常所说的扇区。当然,数据库文件很大,也很多,一定需要占据多个扇区。

题外话:

  • 从上图可以看出来,在半径方向上,距离圆心越近,扇区越小,距离圆心越远,扇区越大
  • 那么,所有扇区都是默认512字节吗?目前是的,我们也这样认为。因为保证一个扇区多大,是由比特位密度决定的。
  • 不过最新的磁盘技术,已经慢慢的让扇区大小不同了,不过我们现在暂时不考虑。
  • 我们在使用Linux,所看到的大部分目录或者文件,其实就是保存在硬盘当中的。(当然,有一些内存文件系统,如: proc , sys 之类,我们不考虑)
  • 建立数据库其实就是在linux下建立一个目录,建立一张表其就是在linux建立一个文件。

所以,最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。

而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。

定位扇区

  • 柱面(磁道): 多盘磁盘,每盘都是双面,大小完全相等。那么同半径的磁道,整体上便构成了一个柱面
  • 每个盘面都有一个磁头,那么磁头和盘面的对应关系便是1对1的
  • 所以,我们只需要知道,磁头(Heads)、柱面(Cylinder)(等价于磁道)、扇区(Sector)对应的编号。即可在磁盘上定位所要访问的扇区。这种磁盘数据定位方式叫做 CHS 。
  • 不过实际系统软件使用的并不是 CHS (但是硬件是),而是 LBA ,一种线性地址,可以想象成虚拟地址与物理地址。系统将 LBA 地址最后会转化成为 CHS ,交给磁盘去进行数据读取。不过,我们现在不关心转化细节,知道这个东西,让我们逻辑自洽起来即可。

结论

我们现在已经能够在硬件层面定位,任何一个基本数据块了(扇区)。那么在系统软件上,就直接按照扇区进行IO交互吗?不是

  • 如果操作系统直接使用硬件提供的数据大小进行交互,那么系统的IO代码,就和硬件强相关,换言之,如果硬件发生变化,系统必须跟着变化
  • 从目前来看,单次IO 512字节,还是太小了。IO单位小,意味着读取同样的数据内容,需要进行多次磁盘访问,会带来效率的降低。
  • 之前学习文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是数据块。

故,系统读取磁盘,是以块为单位的,基本单位是 4KB 。

磁盘访问方式

  • 随机访问(Random Access):扇区地址不连续,磁头需要移动。
  • 连续访问(Sequential Access):扇区地址连续,磁头移动较少,效率较高。

3. MySQL与磁盘交互基本单位

软件理解

MySQL是一个应用层软件,依赖操作系统进行I/O操作。操作系统和磁盘之间的I/O单位是4KB,而MySQL为了提高效率,以16KB为单位先和 OS 进行I/O操作。

Buffer Pool

MySQL在内存中申请了一个大内存空间(Buffer Pool),用于缓存数据。默认大小为128MB。Buffer Pool用于减少磁盘I/O次数,提高查询性能。

I/O流程

  1. MySQL向操作系统请求16KB数据。
  2. 操作系统从磁盘读取4个4KB的数据块,加载到文件缓存区。
  3. 数据从文件缓存区加载到MySQL的Buffer Pool。
  4. MySQL在Buffer Pool中对数据进行处理。
  5. 更新数据时,MySQL将数据写入Buffer Pool,然后通过操作系统刷新到磁盘。

验证

  • 16*1024=16384
  • 磁盘这个硬件设备的基本单位是 512 字节,而 MySQL InnoDB引擎 使用 16KB 进行IO交互。
  • 即, MySQL 和磁盘进行数据交互的基本单位是 16KB 。
  • 这个 16KB 的基本数据单元,在 MySQL 这里叫做 page(注意和系统的page区分),它们是1:4的关系

共识要点

  1. MySQL以16KB page为单位进行I/O
  2. MySQL有Buffer Pool,数据先加载到Buffer Pool,再进行处理。
  3. 减少系统和磁盘I/O次数,一次I/O的数据量越大,效率越高。

以上就是我们学习索引的预备工作,下面正式开始学习索引~

4. 索引的理解

建立测试表

create table if not exists user (
    id int primary key,
    age int not null,
    name varchar(16) not null
) engine=InnoDB;

插入多条记录

insert into user (id, age, name) values (3, 25, 'Alice');
insert into user (id, age, name) values (1, 20, 'Bob');
insert into user (id, age, name) values (2, 22, 'Charlie');

查看插入结果

发现数据是有序的,这是因为MySQL会默认按照主键进行排序。

我们向一个具有主键的表中,乱序插入数据,发现数据会自动排序。谁做的?为什么这么做?

为了解决这个问题,我们需要重新探讨一下MySQL中的Page概念。

1. 重谈Page

数据读取流程

  • 磁盘上的文件数据:首先会被读到操作系统的文件缓存区中。
  • MySQL的Buffer PoolMySQL在启动时会为自己申请一个Buffer Pool,用于缓存数据。MySQL与操作系统之间进行I/O交互的基本单位是16KB,这是为了提高效率,减少I/O成本。

Page的概念

  • Page:MySQL的基本数据单位,大小为16KB。
  • Buffer Pool:可以加载多个Page,使用双向链表连接。

管理Page的方法

  • 先描述,再组织不要简单地将Page认为是一个内存块,Page内部也必须写入对应的管理信息。
  • 链表管理:所谓在MySQL中申请一个Page,实际上是new一个Page对象。然后将所有Page用“链表”的形式管理起来。我们在Buffer Pool内部对MySQL中的Page进行了建模。

2. 为什么IO交互要用Page

Page的重要性

  • 问题: MySQL和磁盘进行I/O交互时,采用Page方案的原因是为了提高效率。如果每次只加载需要的数据,例如查找id=2的记录,第一次加载id=1,第二次加载id=2,一次一条记录,那么就需要2次I/O。如果要找id=5,那么就需要5次I/O。
  • 批量加载:但如果这5条记录(或更多)都被保存在一个Page中(16KB,能保存很多记录),那么第一次I/O查找id=2时,整个Page会被加载到MySQL的Buffer Pool中完成一次I/O。此后如果查找id=1,3,4,5等,完全不需要进行I/O,而是在内存中进行。因此,在单Page内大大减少了I/O的次数。

局部性原理

  • 不能严格保证:我们不能严格保证用户下次查找的数据一定在同一个Page内,但有很大概率,因为有局部性原理。
  • 主要矛盾:IO效率低下的主要矛盾不是单次I/O数据量的大小,而是I/O的次数
3. 有主键的表插入数据时的排序

现象

  • 乱序插入,有序查询:我们向一个具有主键的表中,乱序插入数据,发现数据会自动排序。
  • 结论:数据最终以Page为单位进行管理,而Page是先描述后组织的

4. 单个Page与多个Page

4.1 单个Page

Page的定义

  • Page:一个Page可以看作是一个大的结构体,里面可以放很多数据,也有它自己的属性。
  • 数据承载:一个Page可以承载一部分数据,而一个文件可能很小也可能很大,因此MySQL建立的表可能会是一个或多个Page构成。

Page的特性

  • 大小:在MySQL中,每个Page的大小都是16KB。
  • 双向链表:Page使用prevnext指针构成双向链表,方便管理和遍历。

主键与排序

  • 主键:MySQL会默认按照主键对数据进行排序。
  • 无主键:如果没有主键,默认插入的顺序就是查询时的顺序。

为什么数据库在插入数据时要对其进行排序呢?我们按正常顺序插入数据不是也挺好的吗?

  • 目的:插入数据时排序的目的是优化查询的效率。Page内部的数据记录实质上是一个链表结构,链表的特点是增删快,查询修改慢。因此,有序的结构在查找时更加高效。
  • 优势:有序的数据在查找时从头到尾都是有效查找,没有任何一个查找是浪费的,而且如果运气好,可以提前结束查找过程。

4.2 多个Page

单Page的功能:在查询某条数据时,直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。

多Page的管理

  • 数据量大:如果有1千万条数据,需要多个Page来保存,多个Page彼此使用双链表链接起来,每个Page内部的数据也是基于链表的。
  • 线性查找:查找特定一条记录时,需要进行线性查找,效率较低,要遍历一万多条。

页目录的引入

  • 目录的作用:类似于书籍的目录,多花了空间但提高了效率。
  • 所以,目录,是一种“空间换时间的做法”

目录

单Page目录

牺牲page一部分保存数据的空间,把腾出来的空间用来保存目录,这所谓的目录里面只有两个字段

  • 第一个是它所指向起始位置的key值
  • 第二它有一个指针字段指向这条记录的起始位置

所以未来在查找key的时候,不需要在数据记录里面查找了,而是去目录中找

  • 先找到目录中对应key值的所处的起始位置
  • 然后在根据指针找到这条记录
  • 然后根据这条记录在向下遍历。
  • 虽然最后我们依旧需要遍历,但是是一个很小的子序列遍历了。效率也就大大提高了。

那么当前,在一个Page内部,我们引入了目录。比如,我们要查找id=4记录,之前必须线性遍历4次,才能拿到结果。现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。

现在我们可以再次正式回答上面的问题了,为何通过键值 MySQL 会自动排序?
可以很方便引入目录。

多Page目录

多Page的必要性

  • Page大小固定:MySQL中每一页的大小只有16KB,单个Page大小固定。
  • 数据量增长:随着数据量的不断增大,16KB不可能存下所有的数据,因此需要多个Page来存储数据。

Page的动态管理

  • 自动扩展:在单表数据不断被插入的情况下,MySQL会在容量不足时,自动开辟新的Page来保存新的数据。
  • 组织方式:通过指针的方式,将所有的Page组织起来,形成一个链表结构。

我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?

不用担心,可以再加目录页

多级目录:如果保存数据的Page变得非常多,上层的页目录也会变得非常多

  • 可以通过给上层页目录再加一层目录,形成多级目录结构

这就是传说中的B+树呀!没错,至此,我们已经给我们的表user构建完了主键索引。

随便找一个id=?

我们发现,现在查找的Page数一定减少了,也就意味着IO次数减少了,那么效率也就 提高了。


本篇文章小结:

  • Page:MySQL的基本数据单位,大小为16KB,用于提高I/O效率。
  • 主键与排序:MySQL会默认按照主键对数据进行排序,优化查询效率。
  • 多Page管理:通过页目录和B+树结构,提高多Page间的查找效率。

下篇文章继续讲解

  • B+树:节点不存储数据,叶子节点相连,适合范围查找。
  • 聚簇索引与非聚簇索引:InnoDB使用聚簇索引,MyISAM使用非聚簇索引,各有优劣。

http://www.kler.cn/a/375726.html

相关文章:

  • Day22 opencv图像预处理
  • 利用钉钉与金蝶云星空进行付款单自动化集成
  • Apache paimon表管理
  • 常见寄存器
  • 3000字帮你彻底搞懂Java抽象类与接口的区别(含JDK8接口新增三种方法与丰富案例)
  • golang 高阶函数
  • MCU内存结构解析:FLASH、ROM与RAM的功能与区别
  • elasticsearch 8.x 插件安装(六)之Hanlp插件
  • 超萌!HTMLCSS:超萌卡通熊猫头
  • 中仕公考:上海市25年公务员考试今日报名
  • 【开源免费】基于SpringBoot+Vue.JS网上租赁系统(JAVA毕业设计)
  • 网络通信与并发编程(八)I/O模型
  • 重学前端 File、Blob、FileReader 基础知识学习
  • 4、片元着色器之光线步进及其和兰伯特光照模型的结合应用
  • ChatGPT网页正式上线搜索聊天记录功能!埃隆马斯克的xAI正试图筹集数十亿美元|AI日报
  • ctfshow--xss靶场web327-web333(一命速通不了的靶场)
  • 小程序与服务器通信webSocket和UDPSocket
  • 【Java】异常处理见解,了解,进阶到熟练掌握
  • Github上的十大RAG(信息检索增强生成)框架
  • web——upload1——攻防世界
  • JBoss 6.x中间件监控指标详解
  • 《Python游戏编程入门》注-第4章6
  • C# 图片工具类,缩略图、加水印、调整光暗和灰度、翻转图片等...
  • npm入门教程1:npm简介
  • django重写响应对象
  • 基于随机森林的智能手机用户行为分类及流量预测分析