如果直接给VARCHAR(255)字段创建全量索引,绝大多数情况下都会被卡住。原因很明确:在utf8mb4编码下,它的理论最大索引字节数可达到1020,已经超过InnoDB默认的767字节限制,MySQL通常会立即报错:ERROR 1071。更稳妥、也更符合 MySQL 索引优化实践的做法,是显式指定前缀索引长度,而且这个长度不能凭经验随手填写,必须结合实际数据的区分度来计算,比如使用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 直接拒绝。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字段:它不能直接建立常规完整索引,CREATE INDEX idx ON t(remark(20))才是合法写法,否则会直接报错
真正困难的地方,不只是算出一个数字,而是要清楚背后的取舍——索引长度设得太短,区分度不足,查询效果不理想;设得太长,又会让 B+ 树更深、缓存命中率下降、磁盘占用明显增加。更稳妥的做法是:每次调整前先抽样评估,再验证执行计划,最后再上线实施,少任何一步都可能埋下性能隐患。
