MySQL索引核心分类系统性知识体系
一、索引分类整体框架
MySQL索引可以从四个核心维度进行分类,不同维度之间存在交叉关系:
| 分类维度 | 核心类型 | 本质区别 |
|---|---|---|
| 物理存储维度 | 聚簇索引、非聚簇索引 | 索引与数据是否存储在一起 |
| 功能层级维度 | 主键索引、二级索引 | 是否作为数据组织的主键 |
| 字段数量维度 | 单列索引、联合索引 | 索引包含的字段数量 |
| 优化用途维度 | 覆盖索引、前缀索引 | 针对特定场景的优化手段 |
关键关系:
- InnoDB中,主键索引就是聚簇索引
- 所有非聚簇索引都属于二级索引
- 单列索引和联合索引既可以是主键索引,也可以是二级索引
- 覆盖索引和前缀索引是特殊的二级索引
二、物理存储维度:聚簇索引 vs 非聚簇索引
这是理解InnoDB和MyISAM性能差异的最核心分类,决定了数据的组织方式和查询流程。
2.1 聚簇索引(Clustered Index)
定义:聚簇索引不是一种特殊的索引类型,而是数据本身按某个索引键值的顺序物理存储的方式。B+树的叶子节点直接存储完整的数据行。
核心特点:
- 索引即数据:找到索引就找到了数据
- 一张表有且仅有一个聚簇索引
- 数据按索引键值有序物理存储
- 叶子节点包含:主键值、事务ID、回滚指针、所有用户列
InnoDB聚簇索引选取规则(优先级从高到低):
- 显式定义的PRIMARY KEY主键
- 第一个所有列都NOT NULL的UNIQUE索引
- 自动生成的6字节隐藏row_id(GEN_CLUST_INDEX)
优点:
- 主键查询极快(只需一次B+树查找)
- 范围查询效率极高(数据物理有序)
- 缓存命中率高(数据与索引一起缓存)
缺点:
- 插入速度严重依赖插入顺序(随机主键会导致页分裂)
- 更新主键列代价极高(会导致数据行移动)
- 二级索引查询需要回表(两次B+树查找)
2.2 非聚簇索引(Non-Clustered Index)
定义:索引与数据行分开存储,B+树的叶子节点不存储完整数据,只存储指向数据的指针。
InnoDB中的非聚簇索引:
- 叶子节点存储索引列值 + 对应记录的主键值
- 所有非聚簇索引都称为二级索引/辅助索引
- 查询流程:二级索引 → 主键值 → 聚簇索引(回表)
MyISAM中的非聚簇索引:
- 所有索引(包括主键索引)都是非聚簇索引
- 叶子节点存储数据行的物理地址
- 索引文件(.MYI)与数据文件(.MYD)分离
优点:
- 维护成本低于聚簇索引
- 占用空间通常更小
- 可以创建多个,满足不同查询需求
缺点:
- 非覆盖查询需要额外的I/O操作(回表)
- 范围查询效率低于聚簇索引
2.3 聚簇索引 vs 非聚簇索引对比
| 特性 | InnoDB聚簇索引 | InnoDB非聚簇索引 | MyISAM所有索引 |
|---|---|---|---|
| 叶子节点内容 | 完整数据行 | 索引列+主键值 | 数据物理地址 |
| 数量 | 1个 | 多个 | 多个 |
| 主键查询 | 1次I/O | 2次I/O(回表) | 2次I/O |
| 数据存储 | 与索引一体 | 与索引分离 | 与索引分离 |
| 范围查询 | 极快 | 较快 | 一般 |
| 插入性能 | 依赖主键顺序 | 较好 | 好 |
三、功能层级维度:主键索引 vs 二级索引
3.1 主键索引(Primary Key Index)
定义:基于表的主键创建的索引,是一种约束+索引的组合。
核心特点:
- 唯一且非空:主键列不允许重复值和NULL值
- 一张表只能有一个主键索引
- InnoDB中,主键索引自动成为聚簇索引
- 隐式创建NOT NULL约束和UNIQUE约束
最佳实践:
- 优先使用自增整数作为主键(避免页分裂,提升插入性能)
- 避免使用UUID、业务字段作为主键(随机写性能差,更新代价高)
- 保持主键尽可能短(减少二级索引的存储空间)
3.2 二级索引(Secondary Index)
定义:除了主键索引之外的所有索引都称为二级索引(也叫辅助索引)。
常见的二级索引类型:
- 普通索引(INDEX/KEY):最基本的索引类型,无唯一性限制
- 唯一索引(UNIQUE):索引列值必须唯一,但允许有多个NULL值
- 联合索引:由多个列组合而成的索引
- 前缀索引:只对字符串的前n个字符建立索引
- 全文索引(FULLTEXT):用于全文搜索的特殊索引
核心特点:
- 一张表可以有多个二级索引
- InnoDB中,二级索引叶子节点存储主键值
- 查询时通常需要回表到聚簇索引获取完整数据
唯一索引 vs 普通索引:
- 唯一索引额外保证了数据的唯一性
- 插入/更新时,唯一索引需要检查唯一性,性能略低
- 唯一索引的查询性能与普通索引基本相同
四、字段数量维度:单列索引 vs 联合索引
4.1 单列索引(Single-Column Index)
定义:只包含一个字段的索引。
适用场景:
- 查询条件中经常单独使用该字段
- 该字段的选择性很高(不重复值多)
- 简单的等值查询或范围查询
示例:
CREATE INDEX idx_user_name ON users(name);
4.2 联合索引(Composite Index)
定义:由两个或多个字段组合而成的索引。
核心原理:
- B+树的键值由多个字段按定义顺序拼接构成
- 排序规则:先按第一列升序,第一列相同时再按第二列升序,依此类推
- 类似于电话簿的编排方式:先按姓氏,再按名字
最左前缀匹配原则:
查询条件必须从联合索引定义的最左侧字段开始连续匹配,才能有效触发索引。
对于索引(a,b,c),有效的查询组合:
a = ?a = ? AND b = ?a = ? AND b = ? AND c = ?a > ?或a BETWEEN ? AND ?a = ? AND b > ?或a = ? AND b BETWEEN ? AND ?
失效场景:
b = ?(缺少第一列a)b = ? AND c = ?(缺少第一列a)a = ? AND c = ?(缺少中间列b,c无法使用索引范围查找)
MySQL 8.0.13+新特性:索引跳跃扫描(Index Skip Scan)
- 在某些条件下,即使查询条件不包含最左列,优化器也能使用联合索引
- 适用于第一列基数较低的情况
联合索引设计原则:
- 最左前缀优先:将查询中最常用的字段放在最左边
- 选择性高优先:当不需要考虑排序和分组时,将选择性最高的列放在前面
- 字段数控制:联合索引的字段数不超过3-4个
- 覆盖查询优先:尽量让索引包含查询所需的所有字段(覆盖索引)
示例:
CREATE INDEX idx_user_age_status ON users(age, status);
-- 有效查询
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE age = 25 AND status = 'active';
-- 无效查询
SELECT * FROM users WHERE status = 'active';
五、优化用途维度:覆盖索引 vs 前缀索引
5.1 覆盖索引(Covering Index)
定义:如果一条SQL查询需要的所有列(SELECT、WHERE、ORDER BY、GROUP BY涉及到的列)都存在于索引中,MySQL可以直接从索引返回结果,完全不用回表,这样的索引称为覆盖索引。
核心优势:
- 避免回表:减少B+树查找次数,从2次变为1次
- 减少I/O操作:索引体积远小于全表数据
- 提升缓存命中率:索引页比数据页更容易留在内存中
- 在EXPLAIN输出的Extra列中,标志是Using index
设计原则:
- 优先覆盖高频查询
- 避免过度索引:不要为了覆盖而添加过多字段
- 利用联合索引实现覆盖:将查询需要的字段都加入联合索引
示例:
-- 创建联合索引
CREATE INDEX idx_orders_user_status_amount ON orders(user_id, status, amount);
-- 覆盖索引查询(无需回表)
SELECT amount FROM orders WHERE user_id = 100 AND status = 'paid';
-- EXPLAIN Extra: Using index
-- 非覆盖索引查询(需要回表)
SELECT * FROM orders WHERE user_id = 100 AND status = 'paid';
-- EXPLAIN Extra: 无Using index
5.2 前缀索引(Prefix Index)
定义:只对字符串类型字段的前n个字符建立索引,而不是对整个字符串建立索引。
核心优势:
- 显著减少索引占用的磁盘空间
- 提升索引查询速度(索引页可以存放更多索引项)
- 降低索引维护成本
适用场景:
- 字符串字段较长(如VARCHAR(255))
- 字符串的前n个字符已经具有足够的选择性
- 不需要对整个字符串进行排序或分组
设计原则:
- 选择合适的前缀长度:在选择性和空间之间取得平衡
- 计算选择性:
SELECT COUNT(DISTINCT LEFT(column, n)) / COUNT(*) FROM table; - 目标是让前缀的选择性尽可能接近完整列的选择性
限制:
- 无法用于ORDER BY和GROUP BY
- 无法用于覆盖索引
- 不支持LIKE '%xxx'查询(只能用于LIKE 'xxx%')
示例:
-- 对email字段的前10个字符建立前缀索引
CREATE INDEX idx_user_email_prefix ON users(email(10));
-- 有效查询
SELECT * FROM users WHERE email LIKE 'john@%';
-- 无效查询
SELECT * FROM users WHERE email LIKE '%@example.com';
六、索引分类关系总结
6.1 核心索引类型对比
| 索引类型 | 存储特点 | 查询性能 | 维护成本 | 适用场景 |
|---|---|---|---|---|
| 主键索引 | 聚簇存储,叶子节点是完整数据 | 最高 | 高(主键更新代价大) | 主键查询、范围查询、排序 |
| 普通二级索引 | 非聚簇,叶子节点是主键 | 中等(需要回表) | 中等 | 单条件查询 |
| 联合索引 | 非聚簇,多列组合键 | 高(可覆盖) | 较高 | 多条件查询、覆盖查询 |
| 唯一索引 | 非聚簇,保证唯一性 | 中等(需要回表) | 较高(唯一性检查) | 需要保证数据唯一性的列 |
| 覆盖索引 | 包含查询所有列 | 极高(无需回表) | 较高 | 高频查询 |
| 前缀索引 | 只索引字符串前缀 | 中等 | 低 | 长字符串列 |
6.2 交叉关系图
索引
├── 按物理存储
│ ├── 聚簇索引(InnoDB主键索引)
│ └── 非聚簇索引(所有二级索引)
├── 按功能层级
│ ├── 主键索引(通常是聚簇索引)
│ └── 二级索引(非聚簇索引)
│ ├── 普通索引
│ ├── 唯一索引
│ ├── 联合索引
│ ├── 前缀索引
│ └── 全文索引
├── 按字段数量
│ ├── 单列索引
│ └── 联合索引
└── 按优化用途
├── 覆盖索引(特殊的二级索引)
└── 前缀索引(特殊的二级索引)
6.3 索引选型决策树
是否是主键查询?
├─ 是 → 使用主键索引
└─ 否 → 是否是多条件查询?
├─ 是 → 是否经常一起查询?
│ ├─ 是 → 创建联合索引
│ └─ 否 → 分别创建单列索引
└─ 否 → 列是否是长字符串?
├─ 是 → 前缀选择性是否足够?
│ ├─ 是 → 创建前缀索引
│ └─ 否 → 考虑全文索引或其他方案
└─ 否 → 是否需要保证唯一性?
├─ 是 → 创建唯一索引
└─ 否 → 创建普通索引
→ 最后:是否可以设计成覆盖索引?
├─ 是 → 优化为覆盖索引
└─ 否 → 保持原索引
6.4 常见误区澄清
误区:主键索引就是聚簇索引
- 正确:在InnoDB中是对的;在MyISAM中,主键索引也是非聚簇索引
误区:一张表可以有多个聚簇索引
- 正确:一张表只能有一个聚簇索引,因为数据只能按一种方式物理排序
误区:联合索引的字段顺序不影响性能
- 正确:字段顺序至关重要,必须遵循最左前缀匹配原则
误区:覆盖索引是一种特殊的索引类型
- 正确:覆盖索引不是一种独立的索引类型,而是索引的一种使用方式
误区:前缀索引越长越好
- 正确:前缀长度应在选择性和空间之间取得平衡,过长会失去前缀索引的优势
七、索引设计最佳实践
- 选择性原则:优先为选择性高的字段建立索引
- 最小化原则:索引列的长度尽可能小
- 平衡原则:平衡查询性能与写入性能,避免过度索引
- 联合索引优先:对于多列查询,优先使用联合索引而非多个单列索引
- 覆盖索引优先:尽量让高频查询使用覆盖索引,避免回表
- 避免索引失效:不要在索引列上使用函数、运算、隐式类型转换
- 主键设计:使用自增整数作为主键,避免UUID和业务主键
7.1 索引设计原则
- 优先考虑联合索引:一个联合索引可以替代多个单列索引,减少索引数量
- 覆盖索引优先:尽量让索引包含查询所需的所有列,避免回表操作
- 选择性高的列优先:选择性越高,索引的过滤效果越好
- 避免创建过多索引:过多的索引会降低插入、更新和删除的性能
- 避免在低选择性列上创建索引:如性别、状态等只有少数几个值的列
- 避免在频繁更新的列上创建索引:更新索引的代价很高
- 使用自增整数作为主键:避免页分裂,提高插入性能
7.2 索引使用注意事项
- 避免在索引列上使用函数或表达式:会导致索引失效
- 避免使用!=或<>操作符:会导致索引失效
- 避免使用IS NULL或IS NOT NULL:会导致索引失效
- 避免使用LIKE '%xxx'查询:会导致索引失效
- 避免使用OR连接多个条件:如果OR两边的条件有一个没有索引,会导致全表扫描
- 注意联合索引的最左前缀原则:查询条件必须从最左列开始
- 定期分析和优化索引:删除无用的索引,优化低效的索引
7.3 常见索引失效场景
- 在索引列上使用函数:
WHERE YEAR(create_time) = 2023 - 在索引列上进行计算:
WHERE age + 1 = 20 - 使用!=或<>操作符:
WHERE status!= 1 - 使用IS NULL或IS NOT NULL:
WHERE name IS NULL - 使用LIKE '%xxx'查询:
WHERE name LIKE '%张三' - 字符串不加引号:
WHERE phone = 13800138000(phone是VARCHAR类型) - 联合索引不满足最左前缀原则:
WHERE b=2 AND c=3(索引是idx_a_b_c) - 使用OR连接多个条件:
WHERE a=1 OR b=2(b列没有索引)
八、总结
索引是数据库性能优化的核心,理解各类索引的特点和适用场景是设计高效数据库的关键。
- 聚簇索引决定了数据的物理存储顺序,查询速度最快,但插入和更新代价高
- 二级索引是除主键索引之外的所有索引,查询时通常需要回表操作
- 联合索引基于多个列创建,遵循最左前缀原则,可以替代多个单列索引
- 覆盖索引包含了查询所需的所有列,避免了回表操作,性能极高
- 前缀索引只对字符串的前N个字符创建索引,可以减少索引大小
在实际应用中,应该根据业务查询特点,合理设计和使用索引,避免创建过多或无用的索引,定期分析和优化索引,以达到最佳的数据库性能。