前缀索引非万能解药,虽可减小体积、规避1071错误,但牺牲order by/group by/覆盖索引能力;查不到数据主因前缀过短或查询未对齐左前缀;报错1071常因未同步调整innodb_large_prefix与row_format为dynamic;选长需实测选择性≥全字段95%;替代方案含哈希索引、分段存储等。

直接说结论:前缀索引不是万能解药,它能减小索引体积、绕过 Specified key was too long 错误,但会牺牲 ORDER BY、GROUP BY 和覆盖索引能力,且查不到数据时往往是因为前缀太短或查询条件没对齐。
为什么加了前缀索引还是报错 1071?
常见错误是只改了字段长度,没调底层限制。比如 ALTER TABLE t ADD INDEX idx_token(token(255)) 在 utf8mb4 下实际占 255×4=1020 字节,远超 InnoDB 默认 767 字节上限。
- 先确认当前限制:
SHOW VARIABLES LIKE 'innodb_large_prefix';和SHOW TABLE STATUS LIKE 't';看Row_format是否为DYNAMIC或COMPRESSED - 若为
REDUNDANT或COMPACT,即使开了innodb_large_prefix,仍卡在 767 字节 - 必须同时满足:参数开启 + 表格式为
DYNAMIC+ 前缀长度 ≤ 3072 ÷ 字符集单字符字节数(utf8mb4 下最多 768 字符) - RDS MySQL 需在控制台修改参数,不能仅靠
SET GLOBAL
怎么选前缀长度才不丢数据?
选太短,大量值前缀重复,索引选择性暴跌;选太长,又回到原问题。核心是用真实数据算选择性,而不是拍脑袋定 191。
- 先查全字段选择性:
SELECT 1.0 * COUNT(DISTINCT token) / COUNT(*) FROM t; - 再试不同前缀:
SELECT 1.0 * COUNT(DISTINCT LEFT(token, 32)) / COUNT(*) FROM t;、LEFT(token, 64)、LEFT(token, 128) - 目标是让前缀选择性 ≥ 全字段的 95%,比如全字段是 0.98,那前缀 64 跑出来 0.93 就可以接受,128 跑出 0.97 更稳妥
- 特别注意 JWT token 类字段:头部固定(
eyJ...),前 10 个字符可能 90% 都一样,必须往后测
前缀索引查不到数据?检查这三点
不是索引建错了,而是用法踩了坑。MySQL 不会自动“模糊匹配”前缀,它只加速严格左前缀等值或 LIKE 'xxx%' 查询。
-
WHERE token = 'xxx'✅ 可用(前提是完整值以该前缀开头) -
WHERE token LIKE 'abc%'✅ 可用(必须是左通配,且长度 ≤ 前缀长度) -
WHERE token LIKE '%abc'❌ 全表扫描(右模糊) -
WHERE token LIKE 'ab%cd%'❌ 中间有通配,无法利用 -
ORDER BY token❌ 前缀索引不支持排序,会退化为 filesort
比前缀索引更稳的替代方案有哪些?
如果业务需要精确查、排序、去重,或者 token 差异集中在后半段,前缀索引就是硬伤。这时候该换思路。
- 哈希存取:插入时计算
SHA2(token, 256)存进CHAR(64)字段,建普通索引。查时也先哈希再查,无长度限制,无选择性衰减 - 分段存储:把长 token 拆成
token_prefix(前 32 字符)+token_suffix_hash(后半段哈希),联合索引兼顾前缀检索与唯一性校验 - 关联表归一化:如黑名单 token 场景,单独建
blacklist_tokens表,主键即 token,避免在大宽表上硬扛索引 - 不索引:如果只是“是否存在”,且 QPS 不高,用应用层缓存(Redis)兜底,数据库只做最终一致性落库
真正容易被忽略的是:前缀索引一旦建了,后续想改成全字段索引,得先删再加,期间索引不可用;而哈希字段可在线添加、逐步迁移,风险可控得多。











