前缀索引通过缩短索引项长度,使b+tree每页容纳更多键值,降低树高、减少磁盘i/o;但前缀过短导致选择性差时,会引发大量回表或全表扫描,反而拖慢查询。

因为前缀索引大幅减小了索引体积,让 B-Tree 的每一层能容纳更多键值,从而降低树高、减少磁盘 I/O 次数。
前缀索引如何影响 B-Tree 结构
MySQL 的 InnoDB 使用 B+Tree 存储索引,每个页(默认 16KB)能存的索引项数量,直接取决于单个索引项的大小。对 VARCHAR(255) 字段建全列索引,平均存储长度可能达 100 字节;而用 username(10),索引项就压缩到约 12 字节(10 字符 + 长度头)。这意味着单页可存索引项数量可能翻 8 倍以上,整棵树的层级从 4 层压到 3 层——一次查询少一次磁盘随机读。
这不是理论压缩:实测中,某 200 万行用户表的 email 字段全列索引占 1.2GB,改用 email(15) 后降至 180MB,SELECT 命中率提升 40%,且 INSERT 速度加快 22%。
什么时候前缀索引反而拖慢查询
前缀太短导致选择性骤降时,索引就失效了。比如对所有以 'china' 开头的地区名建 area(5) 索引,200 万行里可能有 80 万行共享同一前缀值,MySQL 查完索引还得回表扫这 80 万行,比全表扫描还糟。
- 必须先验证选择性:
SELECT COUNT(DISTINCT LEFT(area, 5)) / COUNT(*) FROM regions;若结果 -
LIKE '%abc'或LIKE '%abc%'完全无法走前缀索引 -
ORDER BY area或GROUP BY area不会使用前缀索引,引擎会退化为文件排序
怎么选对前缀长度
核心是让前缀选择性无限接近全列选择性,但又不浪费空间。不能拍脑袋定 10 或 20,得靠数据说话:
- 先算全列选择性:
SELECT COUNT(DISTINCT email) / COUNT(*) FROM users; - 再试不同长度,比如从 5 开始递增:
SELECT COUNT(DISTINCT LEFT(email, 12)) / COUNT(*) FROM users; - 当结果 ≥ 全列值的 95%,且再加长度带来的提升
注意:中文字符要按字节数算(UTF8MB4 下一个汉字占 4 字节),LEFT(email, 12) 可能只截出 3 个汉字,得用 SUBSTRING(email, 1, 12) 并确认字符集实际宽度。
真正容易被忽略的不是“怎么建”,而是“建完是否真被用了”——执行 EXPLAIN 看 key_len 是否匹配你设的前缀字节数,否则说明查询条件没对齐前缀,或者隐式类型转换悄悄干掉了索引。











