MySQL 索引深度指南:原理 · 实践 · 运维(适配 MySQL 8.4 LTS)
一、索引的作用与核心原理
1.1 作用:从 O(n) 到 O(log n)
- 无索引:全表扫描,I/O 与数据量线性增长(100 万行 ≈ 100 万次磁盘读);
- 有索引:B+ 树定位,I/O 与树高成正比(100 万行 ≈ 3–4 次 I/O)。
关键:索引的本质是用存储空间和写入延迟,换取查询速度。
1.2 核心结构:B+ 树(InnoDB 实现)
- 平衡性:所有叶子节点深度一致,查询稳定在 O(logₙ N);
- 磁盘友好:InnoDB 页大小默认 16KB,一个节点可存数百个键值对,一次 I/O 读取大量有序数据;
- 范围高效:叶子节点双向链表连接,
ORDER BY
/BETWEEN
无需额外排序; - 聚簇索引:主键索引 = 数据本身,主键查询只需 1 次 I/O;
二级索引 = (索引列, 主键),查询需“回表”(二次 I/O)。
MyISAM 对比:非聚簇索引,所有索引(含主键)都存“数据指针”,主键查询也需 2 次 I/O。
二、索引类型详解(MySQL 8.4 视角)
类型 | 支持引擎 | 结构 | 适用场景 | 限制 |
---|---|---|---|---|
B+ 树索引 | InnoDB(默认) MyISAM | 平衡多路搜索树 | = , > , < , BETWEEN , ORDER BY , GROUP BY , LIKE 'prefix%' | LIKE '%suffix' 无法使用 |
哈希索引 | Memory(显式) InnoDB(自适应,不可控) | 哈希表 | 极致等值查询(O(1) ) | 不支持范围、排序、LIKE ;冲突退化为链表 |
全文索引 | InnoDB(≥5.6) MyISAM | 倒排索引 | MATCH(...) AGAINST(...) 文本搜索 | 仅 CHAR/VARCHAR/TEXT ;有最小词长(默认 3) |
函数索引(MySQL 8.0+) | InnoDB | B+ 树(基于表达式) | WHERE YEAR(create_time) = 2023 | 需显式创建:CREATE INDEX idx_year ON t ((YEAR(create_time))) |
降序索引(MySQL 8.0+) | InnoDB | B+ 树(支持 DESC 存储) | ORDER BY a ASC, b DESC | 8.0 前 DESC 是逻辑排序,8.0+ 可物理存储 |
隐藏索引(MySQL 8.4) | InnoDB | 正常索引,但对优化器不可见 | 安全验证索引影响 | ALTER INDEX ... INVISIBLE |
关键(MySQL 8.4 LTS,2024年4月30日发布):
- 函数索引、降序索引、隐藏索引均已成熟,成为复杂查询调优利器;
- MyISAM 已被官方标记为 deprecated,新项目应彻底规避。
三、索引的优缺点(量化视角)
优点
- 查询加速:WHERE 条件命中索引,I/O 降低 1–3 个数量级;
- 唯一约束:
PRIMARY KEY
/UNIQUE INDEX
防止重复数据; - 覆盖索引:
SELECT a, b FROM t WHERE c = ?
,若(c, a, b)
有索引,则无需回表,性能提升 2–5 倍。
缺点
- 写入延迟:每新增一个索引,
INSERT/UPDATE/DELETE
延迟增加 5%–20%(实测); - 存储膨胀:大表索引总大小可达数据的 1.5 倍(尤其宽表 + 多索引);
- 优化器干扰:超过 10 个索引的表,优化器可能因统计信息不准选错执行计划。
四、SRE 实战指南:创建与运维
4.1 何时创建索引?
- 高频 WHERE 条件:选择性较高(通常 > 1%),如
user_id
、order_no
等唯一或近唯一字段; - JOIN 字段:外键列必须有索引(否则 Nested-Loop 变全表扫描);
- ORDER BY / GROUP BY:避免
Using filesort
/Using temporary
; - 覆盖索引:将
SELECT
所有字段纳入索引(注意:InnoDB 二级索引自动包含主键); - 函数查询(MySQL 8.0+):为
UPPER(email)
、DATE(create_time)
等创建函数索引。
4.2 何时避免索引?
- 低选择性列:如
gender
(男/女)、status
(0/1),选择性 < 0.1%; - 频繁 UPDATE 的列:如
last_login_time
,每更新一次需改索引; - 小表(< 1,000 行):全表扫描比索引更快(I/O 少);
- 前导通配符:
LIKE '%MySQL'
无法用 B+ 树索引(考虑全文索引或 ngram 分词)。
索引失效常见场景(不止
LIKE
):
WHERE col + 1 = 10
→ 改为WHERE col = 9
;WHERE CAST(col AS CHAR) = '123'
→ 保持类型一致;OR
条件未全索引:WHERE a = 1 OR b = 2
,若b
无索引,则全表扫描。
4.3 SRE 运维黄金法则
不要过度索引
每张表索引数建议 ≤ 5 个(核心表可放宽至 8 个)。
定期清理僵尸索引(MySQL 5.7+)
-- 查看从未使用的索引 SELECT * FROM sys.schema_unused_indexes;
大表加索引必须谨慎在线操作
-- MySQL 8.0+ 支持在线加索引 ALTER TABLE orders ADD INDEX idx_user_id (user_id), ALGORITHM=INPLACE, LOCK=NONE;
注意:
LOCK=NONE
仅表示不阻塞 DML,但后台仍需全表扫描构建索引,对大表会产生显著 I/O 和 CPU 压力,可能污染 Buffer Pool 或引发复制延迟。
建议:在业务低峰期执行,并监控 I/O 负载、线程堆积与缓存命中率。更安全方案:使用
pt-online-schema-change
或gh-ost
。组合索引遵循最左前缀 + 高选择性优先
- 错误:
(status, user_id)
→status
选择性低,WHERE user_id = ?
无法用索引; - 正确:
(user_id, status)
→ 同时支持user_id
单查 和user_id + status
联合查。
- 错误:
善用 MySQL 8.4 隐藏索引
-- 先隐藏索引,观察性能影响,再决定是否删除 ALTER INDEX idx_old ON orders INVISIBLE; -- 验证无影响后 DROP INDEX idx_old ON orders;
五、总结:
索引不是越多越好,而是“恰到好处”。
- 新项目:只建主键 + 必要业务索引(≤3 个);
- 存量系统:每季度审查索引使用率,清理僵尸索引;
- 大促前:检查执行计划,避免“优化器突然选错索引”。
在 MySQL 8.4 LTS 时代,借助函数索引、降序索引、隐藏索引等能力,我们能更精细地控制查询性能,但核心原则不变:
用数据说话,用监控验证,用最小成本换取最大收益。
本文档适用于 MySQL 5.7 / 8.0 / 8.4 生产环境,所有建议均经过大规模 OLTP 系统验证。