MySQL 索引介绍与索引优化的简单介绍

前言

在 MySQL 的性能优化体系里,索引是最重要、也最容易被误用的一环。很多慢查询并不是因为 SQL 写得有多复杂,而是因为没有合理使用索引;也有很多线上事故,表面看是"数据库慢了",本质上却是索引设计不合理、查询条件失效、索引过多导致写入性能下降。

如果把数据库比作图书馆,那么表数据就是一本本书,索引就是目录。没有目录,你要找一本书只能一本本翻;有了目录,你可以迅速定位到目标内容。但目录也不是越多越好,目录太多不仅占空间,还会让每次新增、修改、删除都变慢。


一、什么是索引

索引(Index)是一种帮助数据库快速定位数据 的数据结构。

在没有索引的情况下,数据库通常需要进行全表扫描 :从第一行找到最后一行,逐行判断是否满足条件。

而有了索引之后,数据库可以先在索引结构中快速找到目标记录的位置,再去表中取出数据,从而大幅减少扫描量。

1.1 索引的本质

索引的本质是:

用额外的存储空间,换取更快的查询速度。

这是一种典型的"空间换时间"策略。

但索引并不是免费午餐。它有三类成本:

  • 存储成本:索引本身要占磁盘空间
  • 维护成本:数据插入、更新、删除时,索引也要同步维护
  • 设计成本:索引设计不合理,反而会拖慢系统

所以,索引不是"加得越多越好",而是要按查询模式设计


二、MySQL 中常见的索引类型

在 MySQL 中,索引不止一种。从不同角度看,可以分为以下几类。

2.1 按逻辑用途分类

1. 主键索引(Primary Key)

主键索引是最重要的索引,具有唯一性,不能为 NULL。

InnoDB 中,主键索引通常就是聚簇索引

2. 唯一索引(Unique Index)

保证列或列组合的值唯一,但允许出现多个 NULL(具体行为依赖引擎与版本)。

常用于用户名、邮箱等字段。

3. 普通索引(Index)

最常见的索引类型,只是提高查询效率,不保证唯一性。

4. 联合索引(Composite Index)

由多个列共同组成的索引。

比如 (user_id, status, created_at)


三、MySQL 索引底层结构

MySQL 的索引实现方式会因存储引擎而不同。

最常见的是 InnoDB,它默认使用 B+ 树 作为索引结构。

3.1 为什么是 B+ 树

B+ 树适合数据库索引,原因主要有:

  • 树的高度低,查找次数少
  • 节点可以存放更多键值,减少磁盘 I/O
  • 叶子节点有序并相互连接,适合范围查询
  • 查询稳定性高,性能可预测

相比之下:

  • 二叉树层级太深
  • 哈希索引不适合范围查询
  • 红黑树虽然平衡,但不如 B+ 树适合磁盘场景

3.2 B+ 树索引示意图

下面这张图可以直接放进你的文章里:
Root 根节点
Internal Node 1
Internal Node 2
Leaf 1: 1,3,5
Leaf 2: 7,9,11
Leaf 3: 13,15,17
Leaf 4: 19,21,23

3.3 B+ 树的特点

  • 非叶子节点只存键值和指针,不存完整数据
  • 所有数据都保存在叶子节点
  • 叶子节点之间有链表指针,便于范围扫描
  • 查找任意记录,最终都要落到叶子层

这意味着:
主键查询、范围查询、排序查询,都能从 B+ 树中受益。


四、InnoDB 的聚簇索引与二级索引

理解 InnoDB 索引,必须掌握一个核心概念:聚簇索引(Clustered Index)

4.1 什么是聚簇索引

在 InnoDB 中,数据行本身就存储在主键索引的叶子节点上

也就是说,主键索引的叶子节点不仅保存主键值,还保存整行数据。

这就是为什么 InnoDB 的主键索引被称为聚簇索引:
索引和数据是"聚在一起"的。

4.2 什么是二级索引

除了主键索引,其他索引都称为二级索引

二级索引的叶子节点不保存整行数据,只保存:

  • 二级索引列的值
  • 对应主键值

因此,通过二级索引查数据时,通常需要经历两步:

  1. 先在二级索引中找到主键值
  2. 再根据主键回到聚簇索引中取整行数据

这个过程叫 回表

4.3 聚簇索引与二级索引示意图

二级索引:name
Alice -> ID=1
Bob -> ID=2
Carol -> ID=3
聚簇索引 / 主键索引
ID=1 -> row1
ID=2 -> row2
ID=3 -> row3

4.4 为什么主键建议短而稳定

因为主键越长:

  • 聚簇索引的每个叶子页能容纳的记录越少
  • 二级索引里存储的主键引用也会变大
  • B+ 树层级可能更高,I/O 更多

所以主键设计建议:

  • 尽量短
  • 尽量稳定
  • 尽量有序
  • 避免频繁变动

常见推荐:

  • 自增整数主键
  • 雪花 ID
  • 分布式场景下有序 ID

而以下主键通常不理想:

  • 很长的字符串主键
  • 频繁更新的业务字段
  • 无序随机 UUID 作为主键

五、联合索引与最左前缀原则

联合索引是生产环境中最常用、也最容易误解的索引形式之一。

5.1 什么是联合索引

比如建立索引:

sql 复制代码
CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);

这个索引按顺序组织数据,数据库会优先按 user_id 排序,再按 status,再按 created_at

5.2 最左前缀原则

联合索引是否生效,遵循最左前缀原则

查询条件必须从索引的最左列开始,才能充分利用索引。

对于 (user_id, status, created_at)

可用索引的查询
sql 复制代码
SELECT * FROM orders WHERE user_id = 1001;
SELECT * FROM orders WHERE user_id = 1001 AND status = 1;
SELECT * FROM orders WHERE user_id = 1001 AND status = 1 AND created_at > '2026-01-01';
部分可用或无法充分利用的查询
sql 复制代码
SELECT * FROM orders WHERE status = 1;
SELECT * FROM orders WHERE created_at > '2026-01-01';

5.3 为什么要遵守最左前缀

联合索引在 B+ 树中的排序是按列顺序进行的。

如果查询跳过了最左列,数据库就无法直接定位到有序范围,只能扫描更多数据。


六、哪些情况会导致索引失效

索引失效并不等于索引不存在,而是查询条件无法有效使用索引。

下面是最常见的失效场景。

6.1 对索引列做函数或表达式运算

sql 复制代码
SELECT * FROM users WHERE DATE(create_time) = '2026-04-17';

这里对 create_time 做了函数运算,数据库通常无法直接使用索引。

更好的写法是:

sql 复制代码
SELECT * FROM users 
WHERE create_time >= '2026-04-17 00:00:00'
  AND create_time < '2026-04-18 00:00:00';

6.2 隐式类型转换

sql 复制代码
SELECT * FROM users WHERE phone = 13800138000;

如果 phone 是字符串类型,而你传入的是数字,可能触发隐式转换,导致索引失效。

6.3 LIKE 前置通配符

sql 复制代码
SELECT * FROM users WHERE name LIKE '%tom';

前面有 %,数据库无法从索引头部开始定位,只能扫描。

如果是:

sql 复制代码
SELECT * FROM users WHERE name LIKE 'tom%';

则通常仍可使用索引。

6.4 OR 条件使用不当

sql 复制代码
SELECT * FROM users WHERE id = 1 OR nickname = 'tom';

如果两个条件都没有合适索引,或者其中一个索引选择性差,可能导致全表扫描。

6.5 范围查询后面的列难以继续利用

在联合索引中,如果前面列使用了范围查询:

sql 复制代码
SELECT * FROM orders 
WHERE user_id = 1001 AND created_at > '2026-01-01' AND status = 1;

如果 created_at 是范围条件,那么后面的 status 列往往难以继续充分利用。

6.6 选择性太差

如果某列只有极少数取值,比如 gender 只有 0/1 两种值,这种列单独建索引通常意义不大。

因为即使走索引,也要扫描大量数据,优化器可能直接选择全表扫描。


七、索引优化的核心原则

索引优化的目标不是"让所有查询都走索引",而是:

让最频繁、最耗时、最有价值的查询,以最小代价拿到最少的数据。

7.1 先优化查询,再优化索引

很多人一上来就建索引,结果越建越乱。

正确顺序应该是:

  1. 找出慢查询
  2. 分析访问模式
  3. 看执行计划
  4. 再设计索引
  5. 验证效果

7.2 索引要服务于高频查询

优先优化:

  • 频繁执行的 SQL
  • 返回数据量大的查询
  • 参与核心业务链路的查询
  • 影响用户体验的慢查询

不要为了一个偶尔执行的报表 SQL 去给整个表乱建一堆索引。

7.3 高选择性的列更适合建索引

选择性越高,索引效果越好。

比如:

  • 用户 ID
  • 订单号
  • 手机号
  • 邮箱
  • 唯一业务编码

这些列通常比"状态字段""布尔字段"更适合单独建索引。

7.4 尽量让索引覆盖查询

如果查询所需字段都能从索引中直接拿到,不必回表,这种索引叫 覆盖索引

例如:

sql 复制代码
SELECT user_id, status
FROM orders
WHERE user_id = 1001;

如果有索引 (user_id, status),那么数据库只需要扫描索引就能返回结果,无需回表。


八、什么是覆盖索引

8.1 覆盖索引的优势

覆盖索引的好处主要有:

  • 减少回表次数
  • 降低随机 I/O
  • 查询更快
  • 缓解主键索引压力

8.2 覆盖索引示意图

查询 user_id, status
联合索引 (user_id, status)
直接返回结果
查询 user_id, status, amount
联合索引 (user_id, status)
需要回表获取 amount

8.3 如何设计覆盖索引

比如业务常查:

sql 复制代码
SELECT id, status, created_at
FROM orders
WHERE user_id = 1001 AND status = 1;

可以考虑设计:

sql 复制代码
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);

这样不仅能支持查询条件,还能尽量减少回表。

但要注意,覆盖索引不是让你无限扩大索引字段。

索引太宽会导致:

  • 索引占空间更大
  • 写入更慢
  • 缓存命中率变差

所以覆盖索引是"有选择地设计",不是"全字段塞进去"。


九、索引顺序怎么设计

联合索引的顺序非常重要。

一般来说,字段顺序应当根据以下因素决定:

  1. 等值查询列优先
  2. 高区分度列优先
  3. 常用于排序/分组的列
  4. 范围查询列放后面

9.1 经验原则

假设有如下查询:

sql 复制代码
SELECT * FROM orders
WHERE user_id = ?
  AND status = ?
  AND created_at BETWEEN ? AND ?
ORDER BY created_at DESC

可能的索引顺序是:

sql 复制代码
(user_id, status, created_at)

原因是:

  • user_idstatus 是等值过滤
  • created_at 是范围和排序字段
  • 顺序合理时能减少扫描并帮助排序

9.2 不合理的例子

如果你建成:

sql 复制代码
(created_at, status, user_id)

那么等值过滤就无法充分发挥作用,查询性能通常会差很多。


十、慢查询如何分析

做索引优化,不能只靠经验,必须结合执行计划和慢查询日志。

10.1 慢查询日志

MySQL 提供慢查询日志,可以记录执行时间超过阈值的 SQL。

通过慢查询日志,可以找到最值得优化的语句。

10.2 EXPLAIN 的作用

EXPLAIN 可以查看 SQL 的执行计划,判断是否走索引、扫描了多少行、是否使用了临时表和文件排序等。

示例:

sql 复制代码
EXPLAIN SELECT * FROM orders WHERE user_id = 1001;

重点关注字段:

  • type
  • key
  • rows
  • Extra

10.3 执行计划常见含义

type

表示访问类型,性能通常从好到坏大致如下:

  • system
  • const
  • eq_ref
  • ref
  • range
  • index
  • ALL

其中 ALL 通常意味着全表扫描。

key

实际使用的索引名。

如果为 NULL,说明没用上索引。

rows

估算需要扫描的行数。

越少越好。

Extra

常见的有:

  • Using index:使用了覆盖索引
  • Using where:还需要额外过滤
  • Using temporary:使用临时表
  • Using filesort:出现文件排序

如果 Using temporaryUsing filesort 同时出现,通常说明需要重点优化。


十一、索引优化实战案例

下面通过几个典型场景来说明索引优化思路。

11.1 场景一:根据用户 ID 查询订单列表

SQL:

sql 复制代码
SELECT id, order_no, status, created_at
FROM orders
WHERE user_id = 1001
ORDER BY created_at DESC
LIMIT 20;
问题分析
  • user_id 是过滤条件
  • created_at 需要排序
  • 结果只需要少量字段
推荐索引
sql 复制代码
CREATE INDEX idx_user_created ON orders(user_id, created_at);

如果还希望减少回表,可以进一步考虑把 status 也纳入索引:

sql 复制代码
CREATE INDEX idx_user_created_status ON orders(user_id, created_at, status);

但是否加入,要看实际查询字段和写入成本。


11.2 场景二:按状态和时间统计订单

SQL:

sql 复制代码
SELECT COUNT(*)
FROM orders
WHERE status = 1
  AND created_at >= '2026-04-01'
  AND created_at < '2026-05-01';
推荐索引
sql 复制代码
CREATE INDEX idx_status_created ON orders(status, created_at);
原因
  • status 是等值
  • created_at 是范围
  • 两者组合适合统计类查询

11.3 场景三:模糊搜索用户名

SQL:

sql 复制代码
SELECT * FROM users WHERE username LIKE 'tom%';
情况

这种前缀匹配可使用索引:

sql 复制代码
CREATE INDEX idx_username ON users(username);

但如果写成:

sql 复制代码
SELECT * FROM users WHERE username LIKE '%tom%';

普通 B+ 树索引就很难发挥作用。

这种需求更适合:

  • 全文索引
  • 搜索引擎
  • 专门的检索系统

十二、索引越多越好吗

答案是否定的。

索引过多常常会带来副作用。

12.1 索引过多的坏处

1. 插入变慢

每次插入数据时,相关索引都要更新。

2. 更新变慢

如果更新的列包含索引字段,索引也要调整。

3. 删除变慢

删除行时,同样要维护索引结构。

4. 占用更多空间

索引本身会占磁盘和缓存。

5. 优化器选择变复杂

索引太多时,优化器的选择空间更大,计划评估也更复杂。

12.2 索引设计的平衡

索引优化的本质是平衡:

  • 查询性能
  • 写入性能
  • 存储成本
  • 维护复杂度

所以,真正好的索引设计不是"最多",而是"刚刚好"。


十三、索引优化的实用建议

下面是一些非常实用的索引优化建议。

13.1 给高频查询建索引

优先服务最常见的查询,而不是所有可能的查询。

13.2 复合条件尽量设计联合索引

不要把多个单列索引堆在一起,很多时候联合索引更有效。

13.3 控制索引长度

尤其是字符串索引,尽量避免无意义的超长索引。

13.4 避免在索引列上做函数运算

把函数放到常量一侧,或者改写查询条件。

13.5 注意排序字段

如果业务经常按某个字段排序,可以把它纳入联合索引。

13.6 定期清理无用索引

长期不用的索引不仅浪费空间,还会拖慢写入。

13.7 使用 EXPLAIN 验证

不要凭感觉判断,实际执行计划最重要。


十四、常见误区

14.1 误区一:只要建了索引就一定快

错。

索引是否生效、是否合适、是否回表、是否选择性高,都很关键。

14.2 误区二:单列索引叠加就等于联合索引

错。

多个单列索引通常不能替代一个设计合理的联合索引。

14.3 误区三:索引列越多越好

错。

索引宽度过大,会影响写入和缓存。

14.4 误区四:所有查询都要走索引

错。

当数据量很小,或者查询返回大部分数据时,全表扫描反而可能更快。

14.5 误区五:主键越随机越好

错。

随机主键会导致页分裂和写入抖动,尤其在 InnoDB 中更明显。


十五、如何判断一个索引是否值得保留

一个索引是否有价值,通常看以下几个问题:

  1. 这个索引是否被高频使用?
  2. 它是否真正降低了查询成本?
  3. 它是否导致了明显的写入开销?
  4. 它是否与其他索引重复?
  5. 它是否适合当前业务查询模式?

如果一个索引:

  • 很少被用到
  • 与其他索引重叠
  • 占用较大空间
  • 维护成本高

那就应该考虑删除或合并。


十六、MySQL 索引优化的完整思路

一个完整的索引优化流程可以概括为:

第一步:定位慢 SQL

通过慢查询日志、APM、监控系统找到慢 SQL。

第二步:分析业务场景

明确这条 SQL 是查详情、分页、统计、筛选还是排序。

第三步:查看执行计划

使用 EXPLAIN 看是否走索引、扫描多少行、有没有回表。

第四步:设计索引

根据过滤条件、排序条件、覆盖字段、选择性来设计索引。

第五步:验证效果

对比优化前后的执行时间、扫描行数和执行计划。

第六步:观察写入影响

索引优化不能只看查询,要观察插入、更新、删除是否变慢。



相关推荐
Greyson11 小时前
CSS如何处理超长文本换行问题_结合word-wrap属性
jvm·数据库·python
captain3761 小时前
事务___
java·数据库·mysql
justjinji2 小时前
如何批量更新SQL数据表_使用UPDATE JOIN语法提升效率
jvm·数据库·python
爱学习的小邓同学2 小时前
MySQL --- MySQL数据类型
数据库·mysql
weixin_580614002 小时前
MySQL存储过程中如何防止SQL注入_使用参数化查询规范
jvm·数据库·python
2401_837163892 小时前
PHP源码开发用台式机还是笔记本更合适_硬件选型对比【方法】
jvm·数据库·python
baidu_340998823 小时前
mysql修改列名会导致程序报错吗_Change Column语法与兼容性
jvm·数据库·python
只说证事3 小时前
会计岗位向管理会计升级,最该补哪些数据分析技能
数据库·数据挖掘·数据分析
a9511416423 小时前
如何加固SQL集群防注入_实施网络层访问控制策略
jvm·数据库·python