适用版本:MySQL 8.0.x
一、为什么要学索引
在 MySQL 调优里,索引几乎是绕不开的主题。很多初学者对索引的第一印象是:“加了索引,查询就会变快。” 这句话方向没错,但并不完整。更准确地说,索引是一种帮助 MySQL 更快定位数据的数据结构,它的核心价值在于:
- 减少需要扫描的数据量
- 降低磁盘 I/O 成本
- 提升排序、分组、连接等操作效率
- 帮助优化器选择更优执行路径
但索引并不是越多越好。索引会额外占用磁盘空间,也会增加 INSERT、UPDATE、DELETE 的维护成本。因此,学习索引,不能停留在“会建索引”,而要理解它的底层原理、类型差异和适用场景。
二、索引到底是什么
可以把索引理解成一本书的目录。
- 没有目录时,要从第一页翻到最后一页找内容
- 有了目录后,可以先定位章节,再快速找到具体页码
数据库也是一样:
- 没有索引:MySQL 可能需要扫描整张表,即全表扫描
- 有索引:MySQL 可以先在索引中找到目标位置,再读取对应记录
需要注意的是,索引本身也是要存储的,它不是“魔法加速器”,而是一种以空间换时间的设计。
三、MySQL 中索引的核心工作原理
在 MySQL 8.0 中,最常见的存储引擎是 InnoDB。日常开发里提到的索引,大多数时候都是指 InnoDB 的索引结构。
1. 索引本质上是有序数据结构
索引不是简单的“字段标记”,而是按一定规则组织起来的数据结构。MySQL 可以利用这种有序性进行:
- 等值查找
- 范围查找
- 排序优化
- 分组优化
- 部分覆盖查询
2. 索引与数据的关系
在 InnoDB 中,表数据和索引关系非常紧密:
- 主键索引(聚簇索引):叶子节点直接保存整行数据
- 普通二级索引:叶子节点保存索引列值 + 主键值
这意味着:
- 用主键查询时,通常能直接拿到整行
- 用二级索引查询时,可能还需要根据主键再回到聚簇索引中查一次完整记录,这个过程常称为回表
3. 索引提高了读性能,但会影响写性能
每次插入、更新、删除数据时,MySQL 不仅要改数据,还要维护相关索引。因此索引越多,写入成本通常越高。
所以索引设计的核心平衡是:
既要让高频查询足够快,也要避免给写入带来不必要的负担。
四、索引的常见分类
MySQL 中索引可以从多个角度分类。为了不混淆,我们按“用途”和“底层实现”两个角度来理解。
1. 按约束和用途分类
(1)主键索引(PRIMARY KEY)
主键索引用于唯一标识一行数据。
特点:
- 值必须唯一
- 不能为
NULL - InnoDB 中主键索引就是聚簇索引
- 一张表只能有一个主键
示例:
CREATE TABLE user_info (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
phone VARCHAR(20)
) ENGINE=InnoDB;
(2)唯一索引(UNIQUE INDEX)
唯一索引要求索引列值不能重复,但通常允许 NULL(具体表现与业务设计相关,建议实际验证)。
示例:
CREATE TABLE account (
id BIGINT PRIMARY KEY,
email VARCHAR(100) UNIQUE,
nickname VARCHAR(50)
) ENGINE=InnoDB;
适合场景:
- 邮箱
- 身份证号
- 订单号
- 用户名
(3)普通索引(INDEX)
最常见的索引类型,不强制唯一,只是为了提升查询效率。
CREATE INDEX idx_name ON user_info(name);
(4)联合索引(复合索引)
一个索引包含多个列。
CREATE INDEX idx_age_city ON user_info(age, city);
它常用于:
- 多条件过滤
- 避免多次回表
- 优化排序和分组
联合索引是高频面试点,也是实际调优中非常重要的一类,下一篇会重点展开。
(5)前缀索引
对于字符串字段,如果完整索引太长,可以只索引前几个字符。
CREATE INDEX idx_email_prefix ON account(email(10));
优点:
- 节省索引空间
- 降低维护成本
缺点:
- 区分度可能下降
- 某些场景下无法完全覆盖查询
- 对排序和精确过滤能力会受影响
(6)全文索引(FULLTEXT)
全文索引用于文本检索,不适合替代普通 B+Tree 索引。
CREATE TABLE article (
id BIGINT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT KEY idx_ft_content(content)
) ENGINE=InnoDB;
适合场景:
- 关键词搜索
- 自然语言检索
不适合:
- 普通等值查询
- 范围查询
(7)空间索引(SPATIAL)
空间索引用于 GIS、地图坐标、地理位置等空间数据场景,普通业务表中相对少见。
2. 按底层数据结构分类
(1)B+Tree 索引
这是 MySQL InnoDB 最常用、最重要的索引结构。
适合:
- 等值查询
- 范围查询
- 排序
- 分组
- 前缀匹配
绝大多数业务索引,底层都是 B+Tree。
(2)Hash 索引
Hash 索引更擅长等值查询,不适合范围查询和排序。
需要注意:
- InnoDB 并不直接把普通用户索引实现为 Hash 索引
- Memory 引擎支持显式 Hash 索引
- InnoDB 中有自适应哈希索引(Adaptive Hash Index),但它是引擎自动优化机制,不等同于我们手工创建的 Hash 索引
(3)R-Tree / 空间结构
主要服务于空间索引场景,一般业务开发接触较少。
五、InnoDB 中两类最关键的索引
理解 InnoDB,最重要的是分清楚:
- 聚簇索引
- 二级索引
1. 聚簇索引
聚簇索引并不是一种“额外索引类型”,而是数据存储方式。
在 InnoDB 中:
- 表数据按主键顺序组织
- 聚簇索引的叶子节点就是整行数据
这也是为什么主键设计很重要:
- 主键过长,会让二级索引也变大
- 主键频繁变更,代价很高
- 随机主键可能导致页分裂更频繁
通常建议:
- 使用简短、稳定、递增的主键
- 常见做法是
BIGINT自增主键或业务上有序的雪花 ID
2. 二级索引
除主键索引以外的其他索引,一般都可以视作二级索引。
二级索引叶子节点保存:
- 索引列值
- 对应主键值
当查询只需要索引中的列时,可以直接返回结果,这叫覆盖索引;如果还要读取其他不在索引中的列,就需要回表。
示例:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_user_id(user_id)
) ENGINE=InnoDB;
执行:
SELECT id, user_id FROM orders WHERE user_id = 1001;
因为 idx_user_id 的叶子节点里本来就有:
user_idid(主键值)
所以该查询可能直接走覆盖索引。
而如果执行:
SELECT * FROM orders WHERE user_id = 1001;
就往往需要回表读取完整行。
六、查看索引的常用工具
理解索引,离不开“会看”。下面是 MySQL 中最常用的几个工具。
1. SHOW INDEX
查看表上的索引信息:
SHOW INDEX FROM orders;
重点字段:
Key_name:索引名Column_name:索引列Seq_in_index:列在联合索引中的顺序Non_unique:是否允许重复Cardinality:基数,粗略反映区分度
2. SHOW CREATE TABLE
查看建表语句及索引定义:
SHOW CREATE TABLE orders;
适合快速确认:
- 主键
- 唯一约束
- 联合索引
- 前缀长度
- 排序方向
3. INFORMATION_SCHEMA.STATISTICS
用系统表查询索引元信息:
SELECT
TABLE_SCHEMA,
TABLE_NAME,
INDEX_NAME,
COLUMN_NAME,
SEQ_IN_INDEX,
NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'demo_db'
AND TABLE_NAME = 'orders';
适合批量排查、脚本化巡检。
4. EXPLAIN
判断 SQL 是否真正用了索引:
EXPLAIN SELECT * FROM orders WHERE user_id = 1001;
EXPLAIN 会在第三篇里专门展开,但在索引学习阶段,至少要先养成一个习惯:
建了索引,不等于一定被用到。执行前后都要看执行计划。
七、从零开始看一个索引示例
下面通过一个小例子感受索引的作用。
1. 建表
CREATE TABLE employees (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
emp_no VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
dept_id INT NOT NULL,
hire_date DATE NOT NULL,
salary DECIMAL(10,2) NOT NULL,
INDEX idx_emp_no(emp_no),
INDEX idx_dept_id(dept_id)
) ENGINE=InnoDB;
2. 按工号查询
SELECT * FROM employees WHERE emp_no = 'E10086';
如果 emp_no 上有索引,MySQL 就不需要扫描整张表,而是可以通过 idx_emp_no 快速定位记录。
3. 按部门查询
SELECT * FROM employees WHERE dept_id = 10;
由于 dept_id 上也有索引,筛选特定部门时效率会高很多,尤其是在大表中更明显。
4. 没有索引的字段查询
SELECT * FROM employees WHERE salary = 15000.00;
如果 salary 没有索引,这类查询就可能触发全表扫描。
这也说明,索引设计要围绕实际查询模式来做,而不是见列就建。
八、索引并不是总能生效
很多慢 SQL 的根源,不是“没建索引”,而是“建了但没用上”。常见原因包括:
1. 查询条件对索引列做了函数或运算
SELECT * FROM orders WHERE DATE(created_at) = '2026-06-01';
这里对 created_at 做了函数计算,通常会影响索引使用。
更好的写法:
SELECT * FROM orders
WHERE created_at >= '2026-06-01 00:00:00'
AND created_at < '2026-06-02 00:00:00';
2. 隐式类型转换
SELECT * FROM user_info WHERE phone = 13800138000;
如果 phone 是字符串类型,但查询写成数字,可能导致优化器无法按预期使用索引。
3. 匹配范围过大
如果筛选结果占全表很大比例,优化器可能认为全表扫描更划算,而不是走索引。
4. LIKE 以通配符开头
SELECT * FROM article WHERE title LIKE '%MySQL';
这类查询通常无法有效使用普通 B+Tree 索引。
而下面这种更容易利用索引:
SELECT * FROM article WHERE title LIKE 'MySQL%';
九、索引设计的常见优化建议
这一部分是落地最常用的经验总结。
1. 给高频查询条件建索引
优先考虑以下列:
WHERE中高频过滤列JOIN关联列ORDER BY列GROUP BY列
2. 优先选择区分度高的列
区分度越高,筛选能力越强,索引价值越大。
例如:
user_id通常区分度高gender只有男/女两个值,区分度低,单独建索引往往意义不大
3. 不要滥建索引
每多一个索引,就多一份维护成本。尤其是写多读少的表,更要控制索引数量。
4. 主键尽量短、稳定、递增
原因包括:
- 聚簇索引叶子节点存整行数据,主键会影响数据组织
- 二级索引叶子节点保存主键值,主键越大,二级索引越大
- 主键更新代价高
5. 尽量使用覆盖索引
如果查询的列都在索引中,就能减少回表,提高性能。
例如:
CREATE INDEX idx_dept_hire ON employees(dept_id, hire_date);
查询:
SELECT dept_id, hire_date
FROM employees
WHERE dept_id = 10;
这类查询就更容易成为覆盖索引。
6. 用联合索引替代多个低效单列索引
如果查询经常是:
WHERE dept_id = ? AND hire_date = ?
那么 INDEX(dept_id, hire_date) 往往比两个单列索引更实用。
十、学习索引时容易混淆的几个点
1. “有索引”不等于“查询一定快”
还要看:
- SQL 写法是否合理
- 返回行数是否过多
- 是否发生回表
- 是否需要排序/临时表
- 优化器是否选择了该索引
2. “建得越多越好”是误区
索引过多会带来:
- 更多磁盘占用
- 更慢的写入
- 更高的维护和变更成本
- 优化器选择变复杂
3. “唯一索引一定比普通索引快很多”并不准确
唯一索引主要解决的是约束唯一性问题,性能差异要结合具体场景看,不能简单绝对化。
4. “索引就是给查询加速”也不完整
索引除了帮助过滤,还能帮助:
- 排序
- 分组
- 连接
- 覆盖读取
十一、一个实用的排查思路
当你怀疑某条 SQL 需要索引时,可以按下面顺序检查:
- 这条 SQL 的过滤条件是什么?
- 这些列是否已有索引?
- 索引顺序是否匹配查询模式?
- 是否发生函数计算、隐式转换、前导模糊匹配?
EXPLAIN是否真的用了索引?- 即使使用索引,扫描行数是否仍然过大?
- 是否可以改成覆盖索引或联合索引?
这个思路很适合日常排查慢 SQL。
十二、小结
这篇文章主要解决了三个基础问题:
- 索引是什么:本质是帮助 MySQL 快速定位数据的数据结构
- 索引有哪些类型:主键索引、唯一索引、普通索引、联合索引、前缀索引、全文索引、空间索引等
- InnoDB 中怎么理解索引:重点掌握聚簇索引、二级索引、回表、覆盖索引
如果只记住一句话,我建议记住这一句:
索引设计不是“给字段贴标签”,而是围绕查询路径组织数据。
下一篇我们继续深入,重点理解 MySQL 中最核心的索引结构:B+Tree 与联合索引。把这部分吃透之后,再看慢 SQL 和执行计划,思路会清晰很多。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!