MySQL——索引

一、索引的概念

索引 是帮助MySQL高效获取数据的数据结构

索引就像是一本书的目录,通过目录我们可以快速定位到数据所在的位置。无需扫描整张表来找数据。

虽然查询速度变快了,但是与之而来的是读写操作增加了更多的IO,但是为了提高检索速度是值得的。

二、使用索引的背景

1.硬件基础(磁盘)

MySQL 将数据持久化存储在磁盘上。磁盘作为机械设备,其 I/O 速度远低于内存和 CPU,频繁的磁盘访问成为系统性能的主要瓶颈。因此,减少磁盘 I/O 是提升 MySQL 查询效率的关键。而索引,正是为解决这一问题而设计的数据结构,它能大幅减少数据扫描量,从而降低磁盘访问次数。

1.1 磁盘的物理结构

盘片:硬盘有多个盘片,每盘片2面

磁头:每面一个磁头

扇区:盘片被分为多个扇形区域,每个扇区存放512字节的数据,是硬盘的最小存储单位

磁道:同一盘片不同半径的同心圆,是由磁头在盘片表面划出的圆形轨迹

柱面:不同盘片相同半径构成的圆柱面,由同一半径圆的多个磁道组成

盘片旋转,磁头摆动,但是磁头和盘面没有接触

1.2 磁盘的存储结构

磁盘寻址的基本单位是扇区(512byte)

定位扇区的方法: CHS定位法:

定位柱面 => 定位磁头(一个磁头对应一个盘面) => 定位扇区

(柱面:Cylinder 磁头:Head 扇区:Sector)

1.选择柱面:将磁头移动到指定的柱面(磁道)。

2.选择磁头:选择对应的磁头,即选择哪个盘面的数据。

3.选择扇区:通过磁盘的旋转找到指定磁道上的目标扇区。

1.3 磁盘的读取/修改

磁盘访问的基本单位是扇区(512byte),但是磁盘如果一次只读取/修改一个扇区,数据量就太小了,效率低。

所以,操作系统 读取/修改 一次磁盘的基本单位一般是4KB(8个扇区)。也就是说,即使只读取/修改1bit的数据,也要将4KB大小的磁盘数据加载到内存中,再进行读取/修改

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

1.4 磁盘随机访问(Random Access)与连续访问(Sequential Access)

**磁盘的连续访问:**两次 I/O 的扇区地址连续,磁头无需大幅移动即可快速读写,效率较高

**随机访问:**两次 I/O 的扇区地址不连续,磁头需要频繁寻道和旋转定位,导致效率低下

因此,减少随机 I/O 是提升磁盘性能的关键。

2.软件基础

由于数据库存在大量 I/O 操作,为提升效率,MYSQL InnoDB 存储引擎以 16KB 的页(page) 为基本单位与磁盘进行数据交互。

理解:

从逻辑上看,MySQL 仿佛直接以 16KB 为单位与磁盘交换数据。但实际作为应用层服务,MySQL 并不直接操作硬件,而是通过操作系统(OS)来管理存储。MySQL 向 OS 发起 16KB 的 I/O 请求,而 OS 与磁盘交互的基本单位通常是 4KB。因此,一个 16KB 的请求会被 OS 拆分为 4 次 4KB 的 I/O 操作,最终由 OS 完成与磁盘的数据交互。

  1. MySQL 在内存中开辟了 Buffer Pool 缓存区域,用于暂存从磁盘读取的数据页。当执行增删查改操作时,MySQL 优先在 Buffer Pool 中操作数据,以此减少频繁的磁盘 I/O。

  2. 修改后的数据页不会直接写入磁盘,而是根据特定策略先通过 write 接口写入操作系统的文件缓冲区(对应文件描述符 fd)。

  3. 操作系统在适当时机(如调用 fsync)将缓冲区中的数据持久化到磁盘,确保数据最终落盘。

理解这一"Buffer Pool → 操作系统缓冲区 → 磁盘"的双层缓冲机制,有助于后续掌握索引如何优化数据页在内存与磁盘间的流动效率。

查看MySQL InnoDB 存储引擎与磁盘进行数据交互的基本单位大小:

sql 复制代码
SHOW GLOBAL STATUS LIKE 'innodb_page_size';

查出来的结果以字节为单位:16384B/1024=16KB。

3.总结

数据库的任何操作都需要通过计算确定数据位置,这意味着数据必须先加载到内存中才能被 CPU 处理。由于磁盘 I/O 是性能的主要瓶颈,减少 I/O 次数便成为优化的核心目标,而索引正是实现这一目标的关键手段。

三、索引的底层实现

0.讲解准备

建立测试表,后面围绕这个表来进行说明:

cpp 复制代码
create table if not exists user (
id int primary key,
age int not null,
name varchar(16) not null
);

查看所建的表:

sql 复制代码
show create table user\G

向表中插入数据:

sql 复制代码
insert into user (id, age, name) values(3, 18, '杨过'),(4, 16, '小龙 女'),(2, 26, '黄蓉'),(5, 36, '郭靖'),(1, 56, '欧阳锋');

1. 数据组织基础:Page

  • MySQL以16KB的**页(Page)**为单位管理数据,每个Page内部通过链表存储记录,并强制按主键排序。

  • 多个Page通过双向链表连接,形成数据文件的物理组织。

2. 单页内优化:页目录

  • 为提升单页内的查找效率,Page内部引入页目录(类似书的目录),通过主键值快速定位记录位置,避免线性遍历。

3. 跨页优化:目录页

  • 当数据量增大、多个Page出现时,为快速定位目标Page,MySQL引入目录页目录页存放"键值+指针"对,键值为指向的Page中最小主键值,指针指向对应Page。

  • 目录页的本质也是页,普通页中存的数据是用户数据 ,而目录页中存的数据是普通页的地址

4. 最终形态:B+树

目录页本身也是Page,可继续向上层叠加,形成多级目录。

最终构建出来的模型:

这种多级目录结构即为B+树,具有以下特点:

  • 非叶子节点只存目录项(键值+指针) ,不存实际数据,因此能存储大量目录项,树的高度较低(矮胖型)

  • 叶子节点存实际数据且所有叶子节点通过链表串联,便于范围查询。

查找时从根节点开始,逐层向下,每次加载一个Page到内存,通过目录项比较快速定位下一层Page,最终到达叶子节点获取数据。

5. 为何高效

  • 减少I/O次数:树的高度低,查询只需加载少量目录页(而非所有数据页),极大降低磁盘I/O。

查询一条记录时,只需要从根节点到叶子节点沿路径加载对应节点page,I/O 次数等于树的高度 。由于根节点常驻内存(或缓存在 Buffer Pool 中),实际磁盘 I/O 次数可能仅为 2~3 次 。相比之下,如果没有索引,可能需要遍历所有page,进行大量 I/O

  • 利用局部性:单页内有序和页目录加速了内存中的查找。

如果查询了一条记录,后续很可能访问相邻记录(如范围查询或连续扫描)。这些相邻记录很可能位于同一个页或相邻页中,而当前页已经加载到 Buffer Pool 中,可直接在内存中访问,无需额外 I/O。

  • 范围查询友好:叶子节点链表支持高效的范围扫描。

当执行范围查询时,首先通过树搜索找到范围起始值所在的叶子页(需要几次 I/O)。然后,沿着叶子节点的链表向后遍历,依次读取后续叶子页,直到达到范围终点。

**对比:**如果叶子节点没有链表,范围查询将需要反复从根节点遍历到叶子节点,产生大量随机 I/O,效率低下。

总结

B+ 树通过降低树高度 减少 I/O 次数,利用页内有序 + 页目录 加速内存查找,通过叶子节点链表优化范围扫描,从而在磁盘 I/O 和内存效率之间取得完美平衡,成为 MySQL 索引的基石。

为什么不使用B树,而使用B+树?

B树:

B+树:

B树与B+树的核心区别如下:

  • 节点结构:B树的每个节点既存储键值指针,也存储实际数据;而B+树的非叶子节点(目录页)只存放键值和指针,所有数据集中在叶子节点。

  • 叶子节点连接:B+树的叶子节点通过链表相连,B树则没有此结构。

因此,InnoDB选择B+树作为索引结构,主要基于两点优势:

  • 树高更低:非叶子节点不存数据,能存放更多目录项,扇出更高,使树变得矮胖,查找目标数据只需加载更少的页,磁盘I/O次数大幅减少。

  • 范围查找高效:叶子节点链表使范围查询可直接顺序遍历,无需多次回溯树,进一步提升了查询性能。

其他数据结构为何不行?

不同的数据结构作为数据库索引时存在各自的局限性,这也反衬出 B+ 树的优势:

  • 链表 :查找时需要线性遍历,效率低下。

  • 二叉搜索树 :在极端情况下可能退化为线性结构,导致查询退化为顺序扫描

  • AVL 树与红黑树 :虽然保持了平衡或近似平衡,但本质上是二叉树,导致树的高度较高(高瘦树)。从根节点到叶子节点的路径较长,意味着查询时需要更多的磁盘 I/O 次数(每次 I/O 加载一个页),效率不如多路平衡树。

  • Hash 索引 :虽然单点查询理论上可达 O(1) 的极高效率,但无法支持范围查询;且 InnoDB 和 MyISAM 引擎并不支持哈希索引。

  • B 树 :非叶子节点既存储键值指针又存储实际数据,导致目录页能存储的目录项数量减少,从而可能增加树的高度, 意味着查询时需要更多的磁盘 I/O 次数。同时,B 树的叶子节点之间没有链表相连,进行范围查找时需要重复从根节点遍历,效率较低。

6.索引的两种存储方案

MySQL索引根据数据与索引的存储关系,分为非聚簇索引聚簇索引两种方案:

  • 非聚簇索引 :索引页与数据页分离存储,索引叶子节点存放数据记录的地址(如MyISAM存储引擎)

  • 聚簇索引 :索引页与数据页集中存储,索引叶子节点直接存放完整的数据行(如InnoDB存储引擎)

6.1主键索引

主键索引是基于主键字段建立的索引

MyISAM 主键索引(非聚簇)

  • 采用B+树结构,叶子节点存储的是数据记录的磁盘地址。索引文件与数据文件分离,通过索引找到地址后,再根据地址读取数据。

示意图:(基于MyISAM 的主键列建立的索引)

InnoDB 主键索引(聚簇)

  • 同样采用B+树结构,但叶子节点直接存储完整的数据行。数据文件本身就是按主键顺序组织的,因此主键索引即数据。

示意图:

6.2 辅助索引(普通索引)

主键索引为我们提供了根据主键 快速定位整行数据的能力,但查询往往需要基于其他列查找用户(例如按照年龄(age))。如果 age 列没有索引,MySQL 就只能进行全表扫描,效率极低。

为了解决这类问题,MySQL 允许用户在其他列上创建辅助索引(也称二级索引或普通索引)。

辅助索引结构与主键索引完全相同(B+ 树),唯一区别是主键索引要求键值唯一,而辅助索引允许重复

MyISAM 辅助索引

MyISAM 辅助索引结构与主键索引完全相同,叶子节点也存储数据记录的地址。

示意图:(基于MyISAM 的非主键列建立的索引)

InnoDB 辅助索引

InnoDB 辅助索引 结构与主键索引完全相同,只是叶子节点不存储数据 ,而是存储对应行的主键值

因此,通过辅助索引查询时,需要先获取主键值,再到主键索引中检索数据行,这个过程称为回表查询

InnoDB 这样设计是为了节省空间------如果每个辅助索引叶子节点都存储完整数据,会造成大量冗余。

示意图:

查看使用 MyISAM / InnoDB 存储引擎的表结构(验证实验)

(创建表时默认使用InnoDB储存引擎)

1.先找到存放MySQL数据库的目录

bash 复制代码
#先切换为root身份,再执行以下语句
cd /var/lib/mysql

2.切换到当前使用数据库目录下

bash 复制代码
cd test_db

3.查看表结构

  1. user2_561.sdi
  • 类型:序列化字典信息文件(Serialized Dictionary Information)

  • 作用 :以 JSON 格式存储了表 user2 的结构定义(如列名、数据类型、索引信息等)。

  1. user2.MYD
  • 类型:MyISAM 数据文件

  • 作用 :这是 MyISAM 存储引擎专用的数据文件,存放表 user2 的所有实际数据记录。文件内容按插入顺序或数据组织方式存储,没有特定的内部索引顺序。

  1. user2.MYI
  • 类型:MyISAM 索引文件

  • 作用 :这是 MyISAM 存储引擎的索引文件,存放表 user2 的所有索引信息(包括主键、普通索引等)。MySQL 通过 B+ 树结构在此文件中组织索引键和指向数据行的指针,查询时先读此文件定位数据记录,再到 .MYD 文件中读取具体数据。

  1. user.ibd
  • 类型:InnoDB 表空间文件

  • 作用 :这是 InnoDB 存储引擎的表空间文件,存放表 user 的所有数据和索引。文件内部采用 B+ 树结构组织数据,既是数据文件也是索引文件(聚簇索引)。

四、索引的操作

1.创建索引

1.1 创建主键索引

  • 方式一:在创建表的时候,直接在字段名后指定 primary key
sql 复制代码
create table user1(id int primary key, name varchar(30));
  • 方式二:在创建表的最后,指定某列或某几列为主键索引
sql 复制代码
create table user2(id int, name varchar(30), primary key(id));
  • 方式三:创建表以后再添加主键
sql 复制代码
create table user3(id int, name varchar(30));
# 创建表以后再添加主键
alter table user3 add primary key(id);

主键索引具有以下特点:

  • 唯一性:一个表最多只能有一个主键索引,可以是单列或复合主键。

  • 自动创建 :在定义主键约束时,MySQL 会自动为该列创建主键索引,无需手动指定 INDEX 关键字。

  • 高效性:由于主键值不可重复且非空,索引结构紧凑,查询效率最高。

  • 约束性:主键索引的列值必须唯一且不能为 NULL。

  • 常用类型:主键列通常选用整型(如 int),以节省存储空间并提升比较效率。

  • 补充说明:如果表中没有定义主键,用户仍然可以为其他列手动创建普通索引或唯一索引,以优化查询性能。

1.2 创建唯一索引(属于普通索引)

  • 方式一:在表定义时,在某列后直接指定unique唯一属性。
sql 复制代码
create table user4(id int primary key, name varchar(30) unique);
  • 方式二:创建表时,在表的后面指定某列或某几列为unique
sql 复制代码
create table user5(id int primary key, name varchar(30), unique(name));
  • 方式三:创建表以后再添加唯一键
sql 复制代码
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);

唯一索引具有以下特点:

  • 数量不限:一个表中可以创建多个唯一索引。

  • 高效查询:基于B+树结构,能快速定位数据。

  • 数据唯一:索引列的值必须全局唯一(但允许存在多个NULL值,因为NULL被视为"未知",不参与唯一性比较)。

  • 与唯一约束的关系唯一约束通过唯一索引实现 ------当为列定义唯一约束时,MySQL会自动创建对应的唯一索引,用于强制数据唯一性并加速查询。

  • 与主键索引的异同 :若在唯一索引列上同时指定 NOT NULL,则该列功能上等同于主键索引(唯一且非空),但主键索引在一张表中只能有一个,而唯一非空索引可以有多个。

1.3 创建普通索引

  • 方式一:在表的定义最后,指定某列为索引
sql 复制代码
create table user8(id int primary key,
    name varchar(20),
    email varchar(30),
    index(name) 
);
  • 方式二:创建完表以后指定某列为普通索引
sql 复制代码
create table user9(id int primary key, name varchar(20), email varchar(30));
alter table user9 add index(name);
  • 方式三:创建一个索引名为 idx_name 的索引
sql 复制代码
create table user10(id int primary key, name varchar(20), email varchar(30));
create index idx_name on user10(name);

普通索引是最基础的索引类型,具有以下特点:

  • 数量不限:一个表中可以创建多个普通索引,在实际开发中使用最为广泛。

  • 适用场景:普通索引不要求索引列的值唯一,因此当某列需要创建索引但允许包含重复值时,应选用普通索引。

1.4 创建复合索引

sql 复制代码
create table itest(id int primary key, name varchar(20), email varchar(30));
alter table itest add index(name, email);

复合索引(也称为联合索引)是基于表中多个字段共同构建的一棵 B+ 树。其核心特点和规则如下:

  • 结构本质 :复合索引将指定的多个列按定义顺序组合成索引键,形成一棵 B+ 树。例如,在 (name, email) 上创建复合索引,索引键按 name 排序,name 相同时再按 email 排序。从 Key_name 中可以看出,该索引通常以第一个字段命名,但实际包含所有指定列。

  • 索引覆盖 :如果查询只需要访问索引中包含的列(例如通过 name 查找 email),那么直接从复合索引中就能获取所需数据,无需回表查询 主键索引。这种利用索引本身满足查询需求的方式称为索引覆盖,可显著提升查询效率。

  • 最左匹配原则 :复合索引遵循最左匹配原则,即查询条件必须从索引的最左列开始才能使用该索引。例如,对于 (name, email) 索引,可以使用 name(name, email) 进行查找,但单独使用 email 作为条件时无法利用该索引。

  • 索引删除 :如果删除复合索引(例如 (name, email) 索引),其对应的整棵 B+ 树都会被移除。这意味着原本依赖该索引的所有列组合(如 name(name, email) 的查询)都将失去索引支持,除非有其他独立索引存在。因此,复合索引是一个整体,并非多个独立索引的简单叠加。

1.5 创建全文索引(扩展)

全文索引是一种专为文本字段(如文章内容、长文本)设计的索引类型,其核心作用是提升在大段文本中进行关键字匹配检索的效率。与普通索引精确查找某一行数据不同,全文索引的目标是快速定位包含指定关键字的列数据,适用于内容搜索场景。在 MySQL 中,全文索引的实现有以下特点和要求:

  • 语言支持:默认只支持英文,无法直接对中文进行全文检索。

  • 中文解决方案:若需对中文实现全文检索,可借助第三方工具,如 Sphinx 的中文版 Coreseek。

创建全文索引

sql 复制代码
# 创建全文索引
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body) # 创建全文索引
)engine=MyISAM;
# 插入数据
INSERT INTO articles (title,body) VALUES
('MySQL Tutorial','DBMS stands for DataBase ...'),
('How To Use MySQL Well','After you went through a ...'),
('Optimizing MySQL','In this tutorial we will show ...'),
('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
('MySQL vs. YourSQL','In the following database comparison ...'),
('MySQL Security','When configured properly, MySQL ...');

查询数据

查询有没有database数据:

  • 普通查询:
sql 复制代码
# 普通查询
select * from articles where body like '%database%';

# 可以用explain工具看一下,是否使用到索引
explain select * from articles where body like '%database%' \G
  • 使用全文索引查询:
sql 复制代码
# 使用全文索引查询
select * from articles where match(title,body) against ('database');

# 用explain工具看一下,是否使用到索引
explain select * from articles where match(title,body) against ('database')\G

2.查询索引

  • 方式一
sql 复制代码
show keys from 表名;
show keys from 表名\G #排版整齐一些
  • 方式二
sql 复制代码
show index from 表名;
show index from 表名\G #排版整齐一些
  • 方式三
sql 复制代码
desc 表名;

key列的含义:

  • PRI:表示该列是主键索引的一部分(或复合主键中的一列)。

  • UNI:表示该列是唯一索引的一部分(允许 NULL 值,但非 NULL 值必须唯一)。

  • MUL :表示该列是非唯一索引(普通索引)的第一列,或者该列是某个复合索引的一部分且不是唯一约束的列。简单来说,MUL 意味着该列的值可以重复,并且在该列上建立了允许重复的索引。

3.删除索引

3.1 删除主键索引

sql 复制代码
 alter table 表名 drop primary key;

3.2 删除普通索引

唯一索引/唯一约束的删除方法与普通索引完全相同

  • 方式一:
sql 复制代码
# 索引名就是show keysfrom 表名中的 Key_name 字段
alter table 表名 drop index 索引名; 
  • 方式二:
sql 复制代码
drop index 索引名 on 表名

4.索引创建原则

  • 频繁查询字段 :经常作为查询条件(即出现在 WHERE 子句中)的字段,应考虑创建索引,以加速数据检索。

  • 唯一性较高字段:对于选择性高(即字段值重复率低)的字段,创建索引效果更好;而唯一性太差(如性别、状态字段)的字段,即使频繁查询,索引效果也有限,通常不建议单独创建。

  • 避免频繁更新:对于经常更新的字段,维护索引的开销较大,可能会影响写性能,因此不适合创建索引。

  • 无关字段不建 :不会出现在 WHERE 子句或查询条件中的字段,无需创建索引,否则只会增加存储和维护成本,无法提升查询效率。

相关推荐
core5121 小时前
深入浅出 Milvus 向量数据库:从核心原理到 Python 实战指南
数据库·python·milvus·向量数据库·语义检索
user_admin_god1 小时前
服务器安装向量数据库-Docker版本
服务器·数据库·docker
什么时候才能变强1 小时前
如何快速高效备份数据库
数据库
七夜zippoe3 小时前
MongoDB聚合框架与性能优化实战指南
数据库·python·mongodb·性能优化·聚合框架
聆风吟º4 小时前
金仓数据库 SQL 防火墙:内核级防护,筑牢 SQL 注入安全防线
数据库·sql·安全·金仓·kingbasees
码以致用4 小时前
StarRocks的向量数据库能力
数据库·ai
2501_945423549 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
gameboy03110 小时前
从MySQL迁移到PostgreSQL的完整指南
数据库·mysql·postgresql
xdl259910 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis