不指定长度就加不了索引,尤其是utf8mb4+VARCHAR(255)组合下必报ERROR 1071;前缀长度必须基于实际区分度确定,通过COUNT(DISTINCT LEFT(col,n))/COUNT(*)≥0.9验证,而非固定用191。

直接结论:不指定长度就加不了索引,尤其是 utf8mb4 + VARCHAR(255) 组合下,会立刻报 ERROR 1071;真正该用多长,得看数据本身的区分度,不是拍脑袋定 191。
为什么 CREATE INDEX idx ON t(col) 会失败?
MySQL 不允许你对过长的字符串字段建完整索引——不是“不推荐”,是根本拒绝执行。utf8mb4 下一个字符最多占 4 字节,VARCHAR(255) 理论最大索引长度是 255 × 4 = 1020 字节,而 InnoDB 默认单列索引上限是 767 字节(老配置)或 3072 字节(需 innodb_large_prefix=ON 且 ROW_FORMAT=DYNAMIC)。1020 > 767 → 直接报错。
你不会看到任何自动截断提示,只会收到:ERROR 1071 (42000): Specified key was too long。
- 查当前限制:
SHOW CREATE TABLE t看ROW_FORMAT和字符集;SELECT @@innodb_large_prefix确认是否启用大前缀 -
CHAR字段不受此限(长度固定),但VARCHAR/TEXT必须显式给长度 - 别迷信 191:它是 767 ÷ 4 的安全上限,不是推荐值
怎么算出真正该用的前缀长度?
核心指标是「区分度」:不重复前缀数量 / 总行数。越接近 1,效果越好。不能只看前 1~3 位就下结论,要测试多个长度并观察拐点。
比如对 email 字段评估:
SELECT COUNT(DISTINCT LEFT(email, 5)) / COUNT(*) AS sel5, COUNT(DISTINCT LEFT(email, 8)) / COUNT(*) AS sel8, COUNT(DISTINCT LEFT(email, 12)) / COUNT(*) AS sel12, COUNT(DISTINCT LEFT(email, 20)) / COUNT(*) AS sel20 FROM users;
- 如果
sel8 = 0.93、sel12 = 0.991、sel20 = 0.994,那 12 就是性价比拐点 - 样本量大时加
LIMIT 10000加速采样,但注意避免因 LIMIT 导致分布失真 - 区分度低于 0.8 就慎用前缀索引——说明前 N 位太“撞车”,索引效率低
哪些查询能走前缀索引,哪些不能?
前缀索引只支持「最左前缀匹配」,本质是它只存了开头那几字符,没法还原全值。
- 能走索引:
WHERE email LIKE 'abc%'、WHERE name = '张三'(等值查询也依赖前缀唯一性) - 不能走索引:
WHERE email LIKE '%@gmail.com'(左模糊)、WHERE email LIKE '%abc%'(中模糊) - 不支持
ORDER BY email或GROUP BY email——因为索引里没有完整值,无法排序或分组 - 无法做覆盖索引扫描:即使你只查
id和email,也必须回表取完整email值
容易被忽略的细节
倒序存储是个冷门但有效的备选方案,尤其当原始前缀区分度极低时(比如所有 URL 都以 https:// 开头)。你可以新增一列 email_reversed 存 REVERSE(email),再对它建前缀索引,查询时也用 REVERSE(?) 匹配。但这会增加写入负担和查询复杂度,只在区分度实在拉不起来时才考虑。
还有个硬伤:前缀索引无法加 UNIQUE 约束,因为 MySQL 不知道你截的是哪部分——哪怕你试 UNIQUE KEY idx_uq (email(10)),它也只是保证这 10 个字符唯一,不是整邮箱唯一。


















