直接给varchar(255)字段加全量索引大概率失败,因utf8mb4下其理论最大索引字节数1020超过innodb默认767字节上限,mysql会直接报错error 1071;必须显式指定前缀长度,并基于实际数据区分度(如count(distinct left(col,n))/count(*)≥0.95)确定最小有效值,而非盲目使用191。

直接给 VARCHAR(255) 字段加全量索引大概率失败,不是“能不能加”,而是 MySQL 会立刻报错 ERROR 1071 (42000): Specified key was too long——尤其在 utf8mb4 字符集下。必须显式指定前缀长度,且这个长度得从数据里算出来,不能拍脑袋定 191。
为什么 ADD INDEX (name) 会报错
因为 InnoDB 默认单列索引上限是 767 字节(老配置),而 utf8mb4 下一个汉字或 emoji 最多占 4 字节。VARCHAR(255) 理论最大索引字节数是 255 × 4 = 1020,超过 767 就被拒。MySQL 不会自动截断,也不会提示“建议用前缀”,它只抛错。
- 查当前限制:
SELECT @@innodb_large_prefix;和SHOW CREATE TABLE t;看ROW_FORMAT是否为DYNAMIC - 若
innodb_large_prefix=OFF或ROW_FORMAT=COMPACT,767 字节就是硬上限 -
CHAR字段不受此限(长度固定),但VARCHAR/TEXT必须显式写长度,比如(name(30))
怎么选前缀长度:看区分度,不是看字符数
前缀长度单位是「字节」,不是「字符」;而区分度决定它是否真能加速查询。191 是 767 ÷ 4 的安全上限,不是推荐值——实际可能 10 就够,也可能要 50。
- 先快速采样估算:
SELECT COUNT(DISTINCT LEFT(name, 10)) / COUNT(*) AS sel10, COUNT(DISTINCT LEFT(name, 20)) AS sel20 FROM users LIMIT 10000; - 目标是找到区分度陡升的拐点:比如
sel20 = 0.85、sel30 = 0.98,那 30 就比 20 更合理 - 避免全表
COUNT(DISTINCT)——大表直接卡住,用LIMIT抽样或先跑ANALYZE TABLE - 如果字段实际最长只有 12 字符(
SELECT MAX(LENGTH(name)) FROM users;),那前缀长度没必要超 12
SHOW INDEX 和 EXPLAIN 必须一起看
建完索引不等于生效。很多人加了 (name(30)) 却发现 WHERE name = ? 还是走全表扫描——因为优化器认为前缀不够覆盖查询条件。
-
SHOW INDEX FROM users;看Sub_part列:如果是30,说明是前缀索引;NULL才是全列索引 -
EXPLAIN SELECT * FROM users WHERE name = 'xxx';看key_len:如果显示30,说明用了索引;如果远小于 30,可能是隐式类型转换或 collation 不匹配 - 等值查询(
=)对前缀长度敏感;范围查询(LIKE 'abc%')只用到前缀开头部分,后面再长也没用
改前缀长度必须删重建,线上操作要小心
MySQL 不支持 ALTER INDEX ... ON ... (col(N)) 直接修改前缀长度。只能先 DROP INDEX 再 ADD INDEX,这个过程默认锁表。
- 确认是否支持
ALGORITHM=INPLACE:ALTER TABLE users ADD INDEX idx_name (name(30)), ALGORITHM=INPLACE, LOCK=NONE;(需满足条件,如引擎为 InnoDB、无全文索引等) - 大表务必避开业务高峰,否则
DROP INDEX阶段就可能阻塞写入 - 别忽略
TEXT字段:它不支持常规 B+Tree 索引,CREATE INDEX idx ON t(remark(20))是唯一合法方式,否则直接报错
真正难的不是算出那个数字,而是你得知道——索引长度选小了,查不准;选大了,B+ 树变深、缓存效率掉、磁盘空间涨得比数据还快。每次改之前,先抽样、再验证、最后上线,少一步都容易翻车。











