MYSQL-索引(上)

发布于:2025-08-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

前言

硬件理解

软件理解

建立共识

一个现象一个结论


前言

MySQL数据库中,索引是一种用于快速定位和访问表中数据的数据结构,在这里我简单的搭建一个场景,希望帮到大家能够更好的认识索引的用处。

当我们公司中存储着900万条员工信息时,我们想要查出此中一个人的工号时,系统最多需要便利900万条消息,查询的速度往往需要5~6秒,而当我们对员工工号建立索引以后,查询时间几乎等于0。

硬件理解

从硬件层面看,磁盘 I/O 效率低效,主要源于其物理结构和工作原理,以下结合资料详细分析:
 
一、磁盘物理构造与机械运动特性
 
磁盘是机械装置,包含盘片、磁头、电机等部件 。读写数据时,磁头需在盘片上精确定位,涉及机械运动:
 
- 寻道过程:磁头要移动到目标磁道(柱面),这依赖音圈马达等机械结构驱动,机械运动本身速度慢,且频繁寻道时,启停、移动的物理惯性会增加耗时。比如随机访问(两次 I/O 请求的扇区地址不连续 ),磁头需频繁大范围移动,产生明显等待时间。
- 旋转延迟:盘片持续旋转,磁头要等目标扇区转到下方才能读写,旋转速度虽相对稳定(如常见硬盘转速 5400、7200 转/分钟 ),但随机访问时,平均需等待半圈左右,进一步拖慢速度 。
 
二、扇区与数据交互的“天然矛盾”
 
- 扇区容量与 I/O 单位不匹配:传统扇区 512 字节(现虽向 4K 扇区迁移,但历史及兼容性因素仍有影响 ),而系统软件 I/O 基本单位是“块”(通常 4K 字节 )。若按硬件扇区交互,读写同样数据内容,需多次访问磁盘扇区,增加 I/O 操作次数。比如读取 4K 数据,用 512 字节扇区就得访问 8 次,频繁触发机械动作,降低效率 。
- 数据存储的离散性:数据库文件等存储在磁盘扇区,大文件会跨多个扇区。定位文件时,需找到所有保存扇区,若扇区分布分散(随机访问场景 ),磁头反复寻道、等待旋转,效率自然低下 。
 

三、磁盘 I/O 自身的“慢特性”
 
相较于 CPU、内存等电子元件的电信号传输速度,磁盘的机械读写机制天生“慢节”奏 。电信号传输近乎光速,而磁盘机械部件(磁头移动、盘片旋转 )的物理动作,以毫秒级为单位,两者速度差巨大,导致磁盘成为系统 I/O 瓶颈,拖累整体效率 。
 

综上,磁盘因机械结构导致的寻道、旋转延迟,扇区与系统 I/O 单位的适配问题,以及自身物理特性与电子元件的速度差距,共同造成了硬件层面 I/O 效率的低效。这也凸显了 MySQL 等软件优化磁盘 I/O(如合理布局数据、利用缓存 )的重要性,通过减少磁盘机械操作、优化数据访问模式,缓解硬件瓶颈影响 。

软件理解

从应用层的 MySQL 数据库到操作系统再到磁盘,数据交互流程涉及多个环节,以下为大家详细介绍:
 
1. MySQL 数据库层的数据处理与请求发起
 
- 查询解析与优化:当应用程序向 MySQL 发送 SQL 请求(比如 SELECT * FROM users WHERE age > 30;  )时,MySQL 的查询解析器会对 SQL 语句进行词法分析、语法分析,将其转化为内部可处理的结构。接着,查询优化器会根据数据库中的统计信息(例如索引使用情况、表的行数等 ),评估多种执行计划,选择最优的方式来执行查询,确定从哪些表获取数据、如何进行表连接、是否使用索引等。
- 存储引擎交互:MySQL 支持多种存储引擎(如 InnoDB、MyISAM 等 ),不同存储引擎管理数据的方式有所差异。查询优化器确定执行计划后,会调用相应存储引擎的接口来获取数据。比如,对于 InnoDB 存储引擎,它会操作其自身的数据结构(如 B+ 树索引 ),定位到符合条件的数据所在的页(通常大小为 16KB ),如果这些页不在内存中,就会产生读取磁盘数据的需求。
 
2. 操作系统层的数据调度与管理
 
- 文件系统接口调用:MySQL 产生磁盘 I/O 请求后,会通过操作系统提供的文件系统接口(如 Linux 下的 read() 、 write() 等系统调用 )来访问磁盘。操作系统的文件系统负责管理磁盘上的文件和目录,它会将 MySQL 的数据请求转化为对具体磁盘文件(数据库文件、日志文件等 )的操作。
- 缓存机制:操作系统通常有文件缓存机制(如 Linux 的页缓存 Page Cache ),会将频繁访问的数据页缓存到内存中。当 MySQL 请求读取数据时,操作系统首先会检查缓存中是否存在所需的数据页,如果存在,就直接从缓存返回数据,避免了实际的磁盘 I/O 操作,提升了数据读取效率。如果缓存未命中,才会向磁盘发起真正的 I/O 请求。
- I/O 调度:操作系统的 I/O 调度器负责管理多个进程的 I/O 请求队列,对磁盘 I/O 操作进行调度。常见的 I/O 调度算法有noop、deadline、cfq 等,不同算法针对不同的应用场景进行优化,比如 deadline 算法可以优先处理超时的 I/O 请求,避免数据长时间等待,以保证 I/O 操作的及时性。
 
3. 磁盘层的数据读写操作
 
- 地址转换:操作系统发出的 I/O 请求中包含逻辑地址(如文件中的偏移量 ),磁盘控制器需要将其转换为磁盘上的物理地址。早期磁盘使用 CHS(柱面 - 磁头 - 扇区)寻址方式,现在多采用 LBA(逻辑块地址 )寻址,然后通过地址映射机制转换为物理地址。
- 机械动作执行:确定物理地址后,磁盘的电机驱动盘片旋转,磁头通过音圈马达移动到目标磁道,等待目标扇区旋转到磁头下方,然后进行数据的读取或写入操作。读取数据时,磁头感应盘片上的磁性变化,将其转换为电信号,再经过电路处理还原为数据;写入数据时,则是将电信号转换为磁信号记录在盘片上。
- 数据传输:完成数据读写后,磁盘将数据通过接口(如 SATA、SAS 等 )传输回操作系统,操作系统再将数据传递给 MySQL,MySQL 进一步处理数据并返回结果给应用程序。
 
4. 数据交互过程中的反向操作(写入场景)
 
当应用程序向 MySQL 发起数据写入操作(如 INSERT INTO users VALUES (...);  )时,流程类似但方向相反。MySQL 会先在内存中对数据进行处理,比如更新相关的索引结构等,然后将数据写入请求发送给操作系统。操作系统同样会先尝试写入缓存,然后根据缓存策略(如脏页刷新机制 ),在合适的时机将数据持久化到磁盘上,完成数据的最终写入。

建立共识

#MySQL 中的数据文件,是以page为单位保存在磁盘当中的。
#MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。
#而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。
#所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。
#为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 BufferPool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。


#为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数。

一个现象一个结论

代码

create table if not exists user (
id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引
age int not null,
name varchar(16) not null
);
mysql> show create table user \G
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`id` int(11) NOT NULL,
`age` int(11) NOT NULL,
`name` varchar(16) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 --默认就是InnoDB存储引擎
1 row in set (0.00 sec)

--插入多条记录,注意,我们并没有按照主键的大小顺序插入哦
mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)
mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)
mysql> insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.01 sec)
mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)
mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.00 sec)

mysql> select * from user; --发现竟然默认是有序的!是谁干的呢?排序有什么好处呢?
+----+-----+-----------+
| id | age | name |
+----+-----+-----------+
| 1 | 56 | 欧阳锋 |
| 2 | 26 | 黄蓉 |
| 3 | 18 | 杨过 |
| 4 | 16 | 小龙女 |
| 5 | 36 | 郭靖 |
+----+-----+-----------+
5 rows in set (0.00 sec)

我们起初乱序插入的数据在后面的结果显示中为什么是有序的?

重谈page

page不仅仅处理io,哪一个是新增数据,哪个是教数据,哪一个需要被去除掉?哪一个需要被淘汰,哪一个需要被维持下来?这些问题不是单单的一个page的io能够完成的。因此它意味着my sql内部存在着大量的page,这就意味着MySQL必须要将多个同时存在的page管理起来。Page需要先描述,再组织,所以不要简单的将page认为是一个内存+块,page内部也必须写入对应的管理信息。

我们将它想象成一个结构体内部定义前后指针,将其存储类型看作链表形式,对其进行相应的建模。

它所存在的意义是它本身加载了16kb内存,这是一个预加载空间。其如果说上层想要访问id为1,2的数据。 Pag内部会提前存储编号为3,4,5的数据,这样有可能减少io的频繁发送。在一定程度上减少了与硬件的交流,提高了运行速率。

1. Page 是基础:MySQL(InnoDB)用 16KB 左右的 Page 存数据/索引,是数据读写的最小单元,也是有序性的“容器”。
2. 乱序插入的处理:应用层插数据(如主键乱序)时,InnoDB 会按主键/索引键,通过 B+ 树找到对应 Page 写入;Page 满了就分裂,始终保证 Page 内、Page 间按键值有序。
3. 查询有序的原因:查数据时,B+ 树会定位到有序的叶子节点 Page 链表,按顺序遍历这些 Page,就能输出有序结果。
 
核心:应用层“乱序插”,但 Page 与 B+ 树在底层强制数据有序,最终查询时直接读有序的 Page 即可。