一、索引的作用与核心原理
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 系统验证。