初识MYSQL —— 索引
前言
什么是索引?
索引就像给数据库加了 “目录”:
- 只执行一条 create index,不用升级硬件、改代码或调 SQL,就能把查询速度从“翻整本书”变成“直接跳页”,成百上千倍地提速。
- 代价是:每次插入、更新、删除都要同步维护这份 “目录”,带来额外磁盘 I/O,写操作明显变慢。
因此,索引最适合读多写少、数据量巨大的场景,用较小的写性能损失换取极大的读性能收益。
常见的索引:主键索引、唯一索引、普通索引和全文索引
存储介质特性
在链接索引之前,先回顾一下硬件磁盘(在博主文章深入了解linux系统—— 文件系统 有讲解磁盘)简单了解即可
1. 磁盘物理结构
- 盘片与扇区:
- 数据保存在磁盘盘片的同心圆磁道上,最小物理单位是扇区(Sector)
- 传统扇区大小:512字节(现代磁盘已有4KB扇区,但兼容模式仍表现为512字节)
- 文件系统视角:多个连续扇区组成块(Block),Linux默认块大小为4KB
- CHS定位法:
- 柱面(Cylinder):所有盘片相同半径的磁道集合
- 磁头(Head):每个盘面对应一个磁头,编号唯一
- 扇区(Sector):磁道上的分段编号
- 通过CHS三元组可唯一定位任意扇区(硬件层面)
- LBA线性地址:
- 系统软件使用逻辑块地址(LBA),屏蔽CHS的硬件细节
- 操作系统自动完成LBA→CHS转换,实现硬件无关性
2. 磁盘访问模式
| 访问类型 | 特征 | 性能影响 |
|---|---|---|
| 连续访问 | 两次IO扇区地址连续 | 磁头移动距离短,效率高 |
| 随机访问 | 两次IO扇区地址跳跃大 | 需重新寻道,效率低 |
关键结论:磁盘随机IO是性能瓶颈,MySQL设计需尽量减少随机访问
MySQL数据库
1. 页(Page)机制
- InnoDB页大小:16KB(可配置为4KB/8KB/32KB,但16KB是默认最优值)
- 与磁盘块的关系:
- 1个InnoDB页 = 32个传统扇区(512字节)或 4个现代扇区(4KB)
- 预读机制:顺序访问时,磁盘控制器会自动预读相邻页,减少未来IO
- 页结构优势:
- 局部性原理:一次IO加载16KB数据,可能覆盖后续查询所需记录
- 减少随机IO:通过页合并写入(Batch Insert)降低磁盘刷新次数
2. 与文件系统交互
- 双缓冲策略:
- InnoDB缓冲池(Buffer Pool):在内存中缓存热点页,默认大小为物理内存的80%
- 操作系统页缓存(Page Cache):缓存磁盘块,可能重复缓存相同数据
- 优化建议:启用
O_DIRECT模式,绕过系统缓存,避免双重缓冲(需权衡稳定性)
- 写操作路径:
- 修改缓冲池中的页(变为脏页)
- 由后台线程按LRU顺序刷新到磁盘(非实时同步)
- 通过**双写缓冲区(Doublewrite Buffer)**保证页写入原子性(防止半页写)
总结:
- 硬件抽象:通过LBA屏蔽CHS复杂性,实现跨平台兼容
- 页机制:16KB页平衡了随机IO延迟与顺序IO吞吐量
- 缓存层次:Buffer Pool + 预读 + 批量刷新,最大化磁盘利用率
- 写入优化:脏页异步刷新 + 双写缓冲,保证性能与一致性兼得
在MYSQL中的数据文件,都是以page为单位保存在磁盘中的。
MYSQL的CURD操作,都需要通过计算,找到对应的插入位置,或者对应要修改或者查询的数据。
任何计算都得先让数据进内存,CPU 只认内存,磁盘数据必须整页(16 KB Page)搬进内存才能用(MYSQL和磁盘交互的基本单位是16KB)
批量用内存,一次换一块:MySQL 启动就划走一大片内存叫 Buffer Pool,专门缓存这些 16 KB 页;命中缓存就直接在内存里算,省去磁盘动作。
为了更高的效率,就要尽可能的减少系统和磁盘IO的次数。
理解索引
在了解索引具体是什么之前,先来看一种现象:
create table stu(id int primary key,age int,name varchar(10)
);
insert into stu values(3,18,'Tom');
insert into stu values(4,19,'Jack');
insert into stu values(1,20,'Lucy');
insert into stu values(5,19,'Lily');
insert into stu values(2,18,'Jerry');

可以看到,表stu中是存在主键的;这里无序插入5条记录,再使用select查询时,查询到的数据是有序的
这里在创建表时,不指明主键(
MYSQL会创建新的一列作为主键,自增长),在查询到的数据顺序和插入数据的顺序是一样的。
理解单个page
MYSQL和磁盘交互(IO)是以page为单位的,在page中存储着数据;
在MYSQL在从磁盘读取时,就会读取一个/多个page页;那在MYSQl中就势必会存在非常多的page页(一个数据表文件是由一个/多个page页组成的)。
MYSQL要管理这些page页,就势必存在相关的描述字段和数据结构(先描述、再组成)。

每一个page,在MYSQL中都是16KB大小,使用prev和nect构成双向链表。
这里因为设置了主键,MYSQL会默认安装主键给我们的数据进行排序;数据是有序且彼此关联的。
理解多个page
通过上述描述,我们理解:在查询某条数据时将一个page页加载到内存中,减少IO的次数,来提高性能。
但是,数据在页内存储时真的如上述那样,单单使用链表结构吗?
如果有上千万条数据,就需要多个page页来保存,多个page页彼此之间使用双链表连接起来;在每一个page页内部的数据也是链表结构,那查找一条记录还是线性查找啊,效率太低的。
单个page页
在平常看书时,书中存在目录,目录中存储了每一个章节对应的页码;
我们想要查看哪一个章节,就可以根据目录找到页码,快速的找到要看的章节。
目录,就是一直空间换时间的方案
在这里,是不是也可以存在一个目录呢,"目录"中存储中编号和指向对应记录的指针。

这里在查找数据时,就只需要遍历page页中的目录就可以判断出是否存在于当前page中。
所以,在创建表时指明主键,插入数据时
MYSQL会自动安装主键进行排序,方便查询计算没有指明主键,
MYSQL也会新增一列作为主键(自增长),并且安装该列进行排序
多个page页
上述在一个page页中加入目录,解决了在一个page页中查找目标记录的效率问题;
但是,一个page页大小只有16KB,随着数据量不断增大,就必定会有多个page页存储数据;
而在page之间,也是需要MYSQL遍历的,遍历也就意味着需要大量的IO;还是存在多次IO的效率问题。
解决方案:给page页也带上目录。
使用一个目录项来指向某一页,这个目录项存放的就是要执行的页中存放的最小数据的键值;
每一个目录项中都存储着对应page页存储数据的最小键值和指向对应page页的指针。

存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page。
**目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址 **

最底层的数据页通过链表连接
这不就是传说中的**B+树**
Innodb建立索引结构使用的数据结构是B+树。

B树和B+树的区别
B树节点既有数据、又有page指针,而B+树,只有叶子结点有数据;目录页,只有键值和page指针。B+树叶子节点全部相连,而B树没有。
聚簇索引 VS 非聚簇索引
MYISAM存储引擎,同样使用B+树作为索引结构;
MYISAM最大的特点是,将索引page和数据page分离。(叶子节点不存储数据,只有对应数据的地址)

相比较于MYISAM,InnoDB存储引擎则是将索引和数据放在一起的。
InnoDB这种将索引和数据放在一起的索引方案,叫做聚簇索引
在
MYSQL中,除了主键索引之外,我们也可以按照其他列信息建立索引,这种索引一般称为普通(辅助)索引;
对于
MYISAM存储引擎,建立普通索引和主键索引没有什么区别,就是主键不能重复,非主键可以重复(唯一键不能重复)而对于
InnoDB存储引擎,在普通索引的叶子节点page中存储的是主键值而不是数据。通过普通索引,找到目标记录就要两次索引:先检索普通索引获取主键,然后再用主键到主键索引中检索到记录。(这个过程,就叫做回表查询)
索引相关操作
1. 主键索引
创建主键索引,在创建主键时,MYSQL就会自动创建主键索引。
三种方式:
- 创建表时,指明主键字段(在字段名后 指定
primary key)
create table test1(id int primary key,name varchar(10)
);
- 创建表时,指明主键字段(在创建表最后,指明一列/多列作为主键)
create table test2(id int,name varchar(10),primary key (id)
);
- 创建完表后,再添加主键
create table test3(id int, name varchar(10) );
alter table test3 add primary key (id);
2. 唯一索引
除了主键以外,在创建唯一键时,MYSQL也会为我们创建唯一索引。
三种方式:
- 创建表时,指明主键字段(在字段名后 指定
unique key)
create table test4(id int primary key,name varchar(10) unique key
);
- 创建表时,指明主键字段(在创建表最后,指明一列/多列作为主键)
create table test5(id int,name varchar(10),primary key (id),unique key (name)
);
- 创建完表后,再添加主键
create table test6(id int primary key, name varchar(10) );
alter table test6 add unique key (id);
- 一张表可以有多个唯一索引;
- 如果在某一列建立唯一索引,必须保证这一列不能有重复数据;
- 在唯一索引上指定
not null,可以等价于主键索引
3. 普通索引
创建普通索引,也是有三种方式:在创建表时,指定索引;创建完表后,指定某列创建普通索引;以及创建指定索引名的索引。
- 创建表时,指定某列为索引
create table test7(id int primary key,name varchar(10),index(name)
);
- 创建完表后,指定某列为索引
create table test8(id int primary key, name varchar(10));
alter table test8 add index(name);
- 创建完表后,以指定列创建名为
index_name的索引
create table test9(id int primary key, name varchar(10));
create index index_name on test9(name);
- 一张表可以有多个普通索引。
- 如果某列要创建索引,但是该列存储重复的值,就要使用普通索引。
4. 查询索引
查询索引相关信息
show keys from tb_name;
show index from tb_name;
desc tb_name;

5. 删除索引
- 删除主键索引
alter table tb_name drop primary key;
- 删除普通索引 方法1
删除普通索引,需要使用索引名,而索引名就是上述查询索引时Key_name字段。
alter table 表名 drop index 索引名;
- 删除普通索引 方法2
drop index 索引名 on 表名;
本篇文章到这里就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws
