MySQL 索引:概念、类型,以及好处和坏处
一、MySQL 索引到底是什么?
可以把索引类比成书的目录:
- 没有索引:查一本 1000 页的书,没有目录,只能从头翻到尾。
- 有索引:有"目录 + 页码",可以直接翻到对应位置。
在 MySQL(尤其是 InnoDB)里,索引本质上是:
按某些列排好序的数据结构(主要是 B+Tree),用额外的空间提高查询速度。
索引记录的内容:
- 索引列的值(例如
id、name、create_time等); - 指向真实数据行的"地址"(InnoDB 里通常是 主键值)。
查询过程大致是:
- 先在索引结构中快速查到匹配的索引记录;
- 再根据记录中的主键回到聚簇索引中取出整行数据(称为"回表");
- 如果是覆盖索引(查询的列都在索引里),则不需要回表。
二、常见的索引类型
1. 按底层数据结构划分
1.1 B+Tree 索引(最常用)
- InnoDB 中,几乎所有主键索引、唯一索引、普通索引、联合索引都是 B+Tree。
- 是一种多叉平衡树:
- 每个节点存放大量 key 和指针;
- 高度一般只有 2~4 层;
- 查找复杂度为 O(logN)。
优点:
- 支持范围查询 :
>、<、>=、<=、BETWEEN; - 支持排序、分组;
- 支持前缀匹配 (
LIKE 'abc%'); - 节点之间有序,叶子节点之间有链表,方便范围扫描。
1.2 Hash 索引
- 在 MEMORY 引擎中常见,InnoDB 内部也会有自适应哈希索引。
- 底层用哈希表,通过 key 算 hash 直接映射到桶。
优点:
- 等值查询(
=、IN)非常快。
缺点:
- 不支持范围查询;
- 不支持有序扫描、排序、前缀匹配;
- 冲突时需要链表或其他机制,性能会退化。
1.3 全文索引(FULLTEXT)
- 针对文本字段(
CHAR、VARCHAR、TEXT)做分词检索:
sql
ALTER TABLE article
ADD FULLTEXT INDEX idx_title_content (title, content);
SELECT * FROM article
WHERE MATCH(title, content) AGAINST ('分布式系统');
应用场景:文章搜索、评论搜索等。
1.4 空间索引(SPATIAL)
- 针对地理空间数据(点、线、多边形)进行加速。
- 多见于地图、LBS 系统。
2. 按逻辑用途划分
2.1 主键索引(PRIMARY KEY)
- 一张表只能有一个主键索引;
- InnoDB 中,主键索引就是聚簇索引(Clustered Index) :
- 叶子节点存放整行数据;
- 表的数据是按照主键顺序存储的。
2.2 唯一索引(UNIQUE)
- 保证索引列的值不重复;
- 查询性能类似普通索引,多了唯一性检查。
2.3 普通索引(INDEX / KEY)
- 只用于加速查询,没有唯一性约束。
2.4 联合索引(多列索引)
例如:
sql
CREATE INDEX idx_user_city_age
ON user (city, age);
特点:
- 遵循最左前缀原则 :
- 可以用于
WHERE city = ?; - 可以用于
WHERE city = ? AND age = ?; - 不能用于单独的
WHERE age = ?。
- 可以用于
2.5 覆盖索引(Covering Index)
如果查询的字段全部包含在某个索引里,就不需要回表。
例如:
sql
CREATE INDEX idx_user_name_age
ON user (name, age);
SELECT name, age
FROM user
WHERE name = 'Tom';
- 只从
idx_user_name_age中就能拿到结果,不访问聚簇索引; - I/O 更少,性能更高。
三、索引的好处
1. 查询速度大幅提升
- 没有索引:全表扫描,扫描 N 行。
- 有索引:通过 B+Tree 查询,复杂度 O(logN),随着数据量增大优势巨大。
常见受益场景:
WHERE条件查询:
WHERE status = 1 AND create_time > '2025-01-01';JOIN关联:JOIN 字段加索引;ORDER BY:如果排序字段有索引,可以利用索引顺序避免额外排序;GROUP BY:索引可以帮助快速分组。
2. 减少排序开销(filesort)
例如:
sql
SELECT *
FROM order
WHERE user_id = 100
ORDER BY create_time DESC
LIMIT 20;
如果建立联合索引:
sql
CREATE INDEX idx_user_time
ON order (user_id, create_time);
则:
- MySQL 可以顺序扫描索引的一段区间,拿到有序的前 20 条;
- 避免额外
filesort和大量磁盘 I/O。
3. 强制唯一性,保证数据正确性
- 唯一索引或主键索引可以防止数据重复:
- 如唯一手机号、唯一用户名、唯一业务编号等。
- 避免业务层自己处理重复逻辑。
4. 帮助优化器生成更优执行计划
良好的索引布局可以:
- 让优化器更准确估算"需要访问多少行";
- 选择更高效的执行计划;
- 减少不必要的行扫描。
四、索引的坏处(成本与风险)
1. 写入性能下降(INSERT / UPDATE / DELETE 变慢)
每次改动数据时,MySQL 不只更新数据行,还需要更新所有相关索引。
一张表如果有很多索引:
- 插入一行要在多棵 B+Tree 中插入记录;
- 更新索引列要在多棵树里做删 + 插;
- 删除记录要在多棵树上删除对应节点。
因此:
- 写多读少的表不适合建太多索引(例如日志表、大量流水表);
- 读多写少的表比较适合加索引(例如配置类、字典类数据)。
2. 占用磁盘与内存空间
- 每个索引都是一棵 B+Tree,要占用磁盘;
- 热点索引页会加载到 buffer pool,占用内存。
如果索引太多:
- 可能占用大量内存;
- 反而把真正频繁访问的数据页挤出去,性能下降。
3. 索引未生效或使用不当
常见问题:
-
没有走到最左前缀:
sqlINDEX idx_a_b_c (a, b, c); -- 这种情况用不上 idx_a_b_c: WHERE b = 1 AND c = 2; -
在索引列上使用函数 / 计算:
sql-- create_time 上有索引,但这样写基本废掉索引 WHERE DATE(create_time) = '2025-01-01'; -
使用无法利用索引的模糊匹配:
sqlWHERE name LIKE '%abc'; -- 前面有通配符,无法利用 B+Tree 顺序 -
索引列区分度太低:
- 例如只有 0 和 1 的
is_deleted字段; - 单独索引往往收益很低,优化器可能直接选择全表扫描。
- 例如只有 0 和 1 的
4. 索引选择不当,反而变慢
- 当一个表有多个索引时,优化器可能会选错索引;
- 有时走索引 + 回表,比直接全表扫描还慢;
- 需要通过
EXPLAIN查看执行计划,必要时:- 调整 SQL 写法;
- 删除干扰性的索引;
- 或用
USE INDEX/FORCE INDEX(一般慎用)。
5. 维护成本高
索引不是"一次性工程",需要随着业务演进进行管理:
- 删除长期不用的索引;
- 避免多人随意添加重复、冗余索引;
- 大表上增加/删除索引本身也是一个重操作。
五、索引用法建议(实战向)
-
优先给这些字段建索引:
- 频繁出现在
WHERE条件中的字段; JOIN关联使用的字段;- 参与
ORDER BY / GROUP BY的字段; - 需要保证唯一性的业务字段(手机、邮箱、业务 ID 等)。
- 频繁出现在
-
多列条件优先考虑联合索引,而不是一堆单列索引:
如经常有查询:
sqlWHERE user_id = ? AND status = ? AND create_time > ?;可以创建:
sqlCREATE INDEX idx_user_status_time ON order (user_id, status, create_time); -
牢记最左前缀原则:
- 有索引
(a, b, c)时:- 支持:
a,a, b,a, b, c的组合; - 不适合:单独
b或c。
- 支持:
- 有索引
-
避免在索引列上做函数 / 计算:
尽量写成:
sqlcreate_time >= '2025-01-01 00:00:00' AND create_time < '2025-01-02 00:00:00';不要写成:
sqlDATE(create_time) = '2025-01-01'; -
控制索引数量:
经验值(不是绝对):
- 一般业务表 3~5 个索引比较常见;
- 超过 8 个索引就要好好审查每一个的价值。
-
养成看执行计划(EXPLAIN)的习惯:
每当你怀疑"索引是不是用了",直接:
sqlEXPLAIN SELECT ...;关注:
type、key、rows等字段。
六、总结
- 索引是用空间换时间的结构,本质是"有序的数据结构(B+Tree 为主)";
- 好处:查得快、能帮助排序/分组、支持唯一约束、让优化器更聪明;
- 坏处:写入变慢、占空间、可能被错误使用,还要持续维护;
- 正确的姿势:
- 以读写比例为前提,平衡索引数量与类型;
- 理解最左前缀、覆盖索引等核心概念;
- 用
EXPLAIN验证执行计划。
合理设计索引是 MySQL 性能优化中最重要的一块,后续可以结合你项目里的具体表结构,一起设计一套"索引方案 + SQL 例子 + EXPLAIN 分析"。