一、前言
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页地去找我们需要查的那个字,速度很慢;如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引底层数据结构存在很多种类型,常见的索引结构有:b 树、 b+ 树 和 hash、红黑树。在 mysql 中,无论是 innodb 还是 myisam,都使用了 b+ 树作为索引结构。
二、索引类型划分
按照数据结构维度划分:
btree 索引:mysql 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。存储引擎 myisam 和 innodb 实现 btree 索引都是使用 b+tree,但二者实现方式不一样(前面已经介绍了)。
哈希索引:类似键值对的形式,一次即可定位。
rtree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 elasticsearch 代替。
全文索引:对文本的内容进行分词,进行搜索。目前只有
char、varchar、text列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 elasticsearch 代替。
按数据结构维度划分的索引类型本文不做详细介绍,本文主要针对以下两种分类做阐述
按“功能/约束”分类:主键索引、唯一索引、常规索引、全文索引
| 分类 | 含义 | 特点 | 关键字 |
|---|---|---|---|
| 主键索引 | 针对于表中主键创建的索引 | 默认自动创建, 只能有一个 | primary |
| 唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | unique |
| 常规索引 | 快速定位特定数据 | 可以有多个 | |
| 全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | fulltext |
按“存储形式/数据组织方式”分类:聚集索引(clustered)、二级索引(secondary)
| 分类 | 含义 | 特点 |
|---|---|---|
| 聚集索引(clustered index) | 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
| 二级索引(secondary index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
三、按功能/约束分类
1.主键索引(primary key)
定义
表的“主键”对应的索引,mysql 用它来唯一标识一行数据。
一张表 只能有一个主键,主键列 不能为 null,并且 必须唯一。
innodb 特点
innodb 中 主键索引 = 聚集索引(后面会解释聚集索引是什么)。
也就是说:数据行本身就“存”在主键索引的 b+tree 叶子节点里。
适用场景
绝大多数表都应该设计主键:自增 id、雪花 id、uuid(不推荐随机 uuid 做主键,容易导致页分裂/碎片)。
必须为表指定主键(如无显式定义,innodb 会自动生成隐藏主键)。
常用于
where user_id = 1001或联表查询。
create table users ( id int primary key, -- 主键索引 username varchar(50) );
2.唯一索引(unique)
定义
约束某列/多列的值必须唯一。
限制列值 不能重复(但大多数情况下允许 null,且多个 null 在 mysql/innodb 中通常是允许的)。
一张表可以有多个唯一索引。
底层实现
innodb 下通常也是 b+tree。
与普通索引的核心差别:写入时会做 唯一性校验。
典型用途
既是“约束”(保证不重复),也是“加速器”(加速查询)
常用于:手机号、邮箱、业务唯一编号
用户表
username、email;业务表的唯一业务号(如order_no)。
create table users ( id int primary key, mobile varchar(20) unique, -- 唯一索引 email varchar(50) unique );
3.常规索引(普通索引 / index)
定义
最基本的索引,只用于加速查询,没有唯一性约束。
底层实现
innodb:一般是 b+tree 二级索引(后面会讲“二级索引”)。
特点
最常用:按条件查、排序、范围查询、join
可以是单列索引,也可以是联合索引(复合索引)
用途
频繁作为
where条件、join条件、order by、group by的列。支持
where status = 'paid'或order by create_time。
例子:单列索引
alter table user add index idx_name(name);
例子:联合索引
alter table user add index idx_name_phone(name, phone);
查询:
select * from user where name='tom' and phone='138...';
👉 更容易走 (name, phone) 的联合索引。
4.全文索引(fulltext)
定义
innodb 从 mysql 5.6 开始支持 fulltext(历史上 myisam 更早支持)。
适合:分词 + 相关性排序
用来做“文本检索”,支持对长文本按词(或按分词)搜索,例如
match(col) against(...)。
实现与特点
innodb 的 fulltext 是专门的倒排索引体系(不是 b+tree)。
更适合:文章、评论、商品描述等“文本搜索”。
注意:中文检索通常需要分词支持(mysql 原生能力有限,很多场景会用 elasticsearch 等专用搜索引擎)。
例子
create table article ( id bigint primary key, title varchar(200), content text, fulltext key ft_content(content) ) engine=innodb;
查询:
select * from article
where match(content) against('mysql 索引' in natural language mode);四、按“存储形式/数据组织方式”分类
1.聚簇索引(聚集索引)
聚簇索引:在 innodb 存储引擎中,聚簇索引通常就是主键索引 。聚簇索引的特点是数据与索引一体化存储,即数据行(整行,不是指针)直接存储在索引的叶子节点中,并且数据按照主键的顺序进行物理存储 。这使得聚簇索引在查询时具有极高的效率,尤其是对于主键查询和范围查询。因为数据是按照主键顺序存储的,所以在进行范围查询(如查询 id 在某个范围内的用户)时,可以利用索引的有序性,快速定位到满足条件的数据。此外,聚簇索引还能利用顺序检测预取机制,提高数据读取的效率。例如,在一个用户表中,以用户 id 为主键创建聚簇索引,当查询用户 id 为 100 的用户信息时,数据库可以直接通过聚簇索引找到对应的叶子节点,获取用户信息,无需进行额外的查找操作。需要注意的是,一张表只能有一个聚簇索引,因为数据的物理存储顺序只能有一种。
概括:用主键查 = 直接定位到叶子节点 = 一次 b+tree 查找拿到整行。
为什么叫“聚集”
因为数据行与索引键“聚在一起”,数据就是索引的一部分。
聚集索引选取规则:
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一(unique)索引作为聚集索引。
如果表没有主键,或没有合适的唯一索引,则innodb会自动生成一个rowid作为隐藏的聚集索引。
聚簇索引的优缺点:
优点:
查询速度非常快:聚簇索引的查询速度非常的快,因为整个 b+ 树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 io 操作。
对排序查找和范围查找优化:聚簇索引对于主键的排序查找和范围查找速度非常快。
缺点:
依赖于有序的数据:因为 b+ 树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 uuid 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
更新代价大:如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
2.非聚簇索引
也称为二级索引,其索引存储的是主键值,而不是实际的数据行 。当使用非聚簇索引进行查询时,首先会根据索引找到对应的主键值,然后再通过主键值在聚簇索引中查找实际的数据行(整行),这个过程称为回表查询 。非聚簇索引支持多列组合索引,适用于多个字段联合查询的场景。例如,在一个订单表中,经常需要根据客户 id 和订单日期进行查询,可以为客户 id 和订单日期创建组合非聚簇索引。虽然非聚簇索引需要回表查询,查询效率相对聚簇索引略低,但在某些情况下,它可以提供更灵活的查询方式。比如,在查询订单表中某个客户在特定日期之后的订单时,通过组合非聚簇索引可以快速定位到满足条件的主键值,然后再通过回表查询获取完整的订单信息。
概括:二级索引的叶子节点存的不是整行,而是“索引列 + 主键值”。
覆盖索引(避免回表)
如果查询需要的列 都在二级索引里(或索引里包含它们),那就不需要回表,这叫 覆盖索引。
例如:
select name from user where age=20;若建了
(age, name)联合索引,就可能直接从索引叶子拿到name,无需回表。
非聚簇索引的优缺点:
优点:
更新代价比聚簇索引要小。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的。
缺点:
依赖于有序的数据:跟聚簇索引一样,非聚簇索引也依赖于有序的数据。
可能会二次查询(回表):这应该是非聚簇索引最大的缺点了。当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

五、图解
1.示例表结构
create table account_example ( id int primary key auto_increment comment '账户id', name varchar(20) comment '姓名', money double(10,2) comment '余额', index idx_name (name), index idx_name_money (name, money) ) engine=innodb; -- 插入数据 insert into account_example(id, name , money) values (1, '张三', 100.00), (3, '李四', 200.00), (7, '王五', 300.00);
id→ 主键索引(聚簇索引)
idx_name(name)→ 普通二级索引
idx_name_money(name, money)→ 联合索引

2.聚簇索引(id)b+tree 示意图:
聚簇索引的b+tree 的叶子节点存放的是整行数据。

特点总结
一张表 只能有一个聚集索引
数据行 按主键顺序物理存储
使用主键查询:
select * from account where id = 3;
👉 一次 b+tree 查找即可拿到整行数据,不存在回表
3.非聚簇索引(idx_name)的 b+tree 示意图:
非聚簇索引的叶子节点中:不存整行数据,只存「索引列值 + 主键值」

二级索引的叶子节点下挂的是该字段值对应的主键值。
4.为什么会发生回表
示例 sql(会回表)
select * from account where name = '李四';
执行过程拆解
step 1:通过二级索引 idx_name 查找
name='李四' ↓ 在 idx_name b+tree 中定位 ↓ 得到主键 id = 3
step 2:根据主键回到聚集索引
id=3 ↓ 在 primary key b+tree 中查找 ↓ 获取完整行数据
📌 这一步称为:回表
本质原因:二级索引不存完整数据
5.联合索引 idx(name, money) 的存储结构
联合索引叶子节点存:(name, money) + id
idx_name_money(name, money) b+tree 示意图:

6.覆盖索引 vs 回表
❌ 示例 1:需要回表
select * from account where name = '李四';
使用
idx_namemoney 不在索引中
必须回表
✅ 示例 2:覆盖索引(不回表)
select name, money from account where name = '李四';
使用
idx_name_money查询字段 全部存在索引叶子节点
无需回表
📌 这就叫:覆盖索引(covering index)
7.总结
| 场景 | 是否回表 | 原因 |
|---|---|---|
| where id = ? | ❌ | 聚集索引叶子节点存整行 |
| 普通二级索引查 * | ✅ | 二级索引不存完整数据 |
| 联合索引覆盖查询字段 | ❌ | 查询字段都在索引中 |
| select * | 几乎一定 | 字段太多,无法覆盖 |
六、关联
| 功能分类 | 在 innodb 的存储形式 |
|---|---|
| 主键索引 | 聚集索引(叶子存整行) |
| 唯一索引 | 通常是 二级索引(除非它被选为聚集索引键) |
| 常规索引 | 二级索引 |
| 全文索引 | 倒排索引体系(不按聚集/二级的 b+tree 逻辑走) |
七、问题
主键为什么不要用随机值(如随机 uuid)?
聚集索引决定了数据行的物理组织顺序
随机插入会导致频繁页分裂、碎片、写放大,性能更差 (自增/有序主键插入更“顺滑”)
二级索引为什么存主键值,而不是物理地址?
因为 innodb 的数据行会移动/页会分裂,存物理地址维护成本高
存主键值更稳定,代价是可能需要回表
建索引的常见收益点
加速
where、join、order by、group by利用覆盖索引减少回表、减少 io
八、总结
innodb 中,主键索引就是聚集索引,数据行存放在主键 b+tree 的叶子节点;
二级索引的叶子节点只保存索引列和主键值,因此在查询非索引字段时需要回表;
当查询字段完全被索引覆盖时,可以避免回表,从而显著提升查询性能。
到此这篇关于mysql索引的原理与性能优化教程的文章就介绍到这了,更多相关索引设计优化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论