字符串字段加索引必须指定前缀长度,否则因utf8mb4下varchar(255)最大索引长度1020字节超过innodb默认767字节限制而报错error 1071;应基于实际数据区分度选择前缀长度,而非固定用191,并通过explain和show index验证索引生效。

字符串字段加索引必须指定前缀长度,否则大概率报错 ERROR 1071 (42000): Specified key was too long;不是“能不能加”,而是“不指定就加不上”——尤其在 utf8mb4 + VARCHAR(255) 这类常见组合下。
为什么 VARCHAR(255) 直接建索引会失败
utf8mb4 下一个汉字/emoji 最多占 4 字节,VARCHAR(255) 理论最大索引字节数是 255 × 4 = 1020 字节。而 InnoDB 默认单列索引上限是 767 字节(老配置)或 3072 字节(innodb_large_prefix=ON 且 ROW_FORMAT=DYNAMIC)。1020 > 767 → 直接 ADD INDEX (name) 就会失败。
你不会看到“自动截断”的提示,MySQL 会直接拒绝执行并抛出错误。
- 查当前表配置:
SHOW CREATE TABLE t;看DEFAULT CHARSET和ROW_FORMAT - 确认是否启用大前缀:
SELECT @@innodb_large_prefix; -
CHAR字段不受此限(长度固定),但VARCHAR/TEXT必须显式给长度
怎么选前缀长度:看区分度,不是拍脑袋定 191
191 是 767 ÷ 4 的安全上限,不是推荐值。真正该用多长,取决于你数据的实际分布。
执行这类采样查询(先 LIMIT 小样本提速):
SELECT COUNT(DISTINCT LEFT(name, 10)) / COUNT(*) AS sel10, COUNT(DISTINCT LEFT(name, 20)) / COUNT(*) AS sel20, COUNT(DISTINCT LEFT(name, 30)) / COUNT(*) AS sel30, COUNT(DISTINCT LEFT(name, 50)) / COUNT(*) AS sel50 FROM users LIMIT 10000;
目标是找到“区分度陡升后的拐点”:比如 sel30 = 0.92、sel50 = 0.992,那 30 可能就够日常等值查询,50 更适合唯一性要求高的场景(如用户名去重)。
- 邮箱字段:通常
LEFT(email, 25)就能覆盖 95%+ 区分度,因为本地名开头差异大,@后域名重复高 - 中文姓名:
name(15)常已足够,超长反而浪费空间 - URL 或商品标题:从 30 起试,电商标题前 40 字基本含核心词
前缀索引能用在哪?不能用在哪?
前缀索引只对左匹配生效,本质是“把字段开头 N 字符当键存进 B+Tree”。它不是万能加速器。
- ✅ 支持:
WHERE name = 'xxx'、WHERE name LIKE 'abc%'、WHERE name IN ('a', 'ab', 'abc') - ❌ 不支持:
WHERE name LIKE '%abc'、WHERE name LIKE '%abc%'、ORDER BY name、GROUP BY name(会触发filesort或临时表) - ⚠️ 注意:
WHERE name = ?能走索引,但优化器无法准确估算选择性,EXPLAIN中rows可能严重低估
如果业务真需要完整排序或模糊中匹配,别硬扛前缀索引——考虑生成列:ALTER TABLE t ADD COLUMN name_hash CHAR(32) AS (MD5(name)) STORED; 再对 name_hash 建索引,更可控。
上线前必须验证的三件事
建完索引不是终点,容易忽略的细节往往导致索引“形同虚设”。
- 用
EXPLAIN SELECT * FROM t WHERE name = 'xxx';看key_len是否接近你设的前缀字节数(比如name(30)在 utf8mb4 下应≈120 字节) - 查
SHOW INDEX FROM t;确认Sub_part列显示的是你设的数字,不是NULL - 上线后盯一周慢查询日志,确认该索引被真实命中——很多前缀索引建了半年,
Handler_read_next几乎为 0
最常被跳过的一步是区分度验证:拿生产数据跑一次 COUNT(DISTINCT LEFT(...)),比凭经验猜 191 或 255 实在得多。索引越短、区分度越高,B+Tree 层级越浅,缓存效率才真正提升。











