MySQL优化器按VARCHAR声明长度预估内存,如utf8mb4下VARCHAR(255)按1020字节/行预分配,远超实际内容,导致排序、临时表等内存虚高,易触发磁盘落盘和性能断崖。

为什么VARCHAR(255)会让InnoDB多分配内存
MySQL优化器在生成执行计划时,会按VARCHAR声明长度预估内存开销,而不是按实际内容。比如VARCHAR(255)在utf8mb4下,优化器默认按最多1020字节(255×4)来算排序、临时表、JOIN缓冲所需空间。
常见错误现象:EXPLAIN FORMAT=TREE里看到Using temporary; Using filesort,但字段实际平均只有20字符——问题就出在声明长度虚高,导致MySQL不敢用更轻量的算法。
- 每行多预分配内存:
VARCHAR(255)vsVARCHAR(50),前者在内存临时表中可能多占800+字节 - 千万级表做
GROUP BY时,VARCHAR(255)可能让tmp_table_size瞬间耗尽,被迫落盘,性能断崖下跌 - 某些连接池(如旧版Druid)会按声明长度分配JDBC
ResultSet缓冲区,造成堆内存浪费
为什么VARCHAR(255)不是索引安全上限
很多人以为“255能建索引”,其实只是碰巧在utf8mb4下191前缀刚好不超767字节限制。但VARCHAR(255)本身并不能直接全字段建索引——你真正能用的,是INDEX(col(191)),而这个191和255毫无关系。
容易踩的坑:
-
VARCHAR(256)和VARCHAR(255)在索引前缀上没区别,但前者长度前缀从1字节变2字节,白白增加B+树节点体积 - 联合索引里含
VARCHAR(255)字段时,InnoDB单索引最大3072字节(开启innodb_large_prefix后),它直接吃掉近1/3容量,压缩其他列可索引长度 -
SHOW INDEX FROM table看不出前缀是否够用,得手动验证区分度:SELECT COUNT(DISTINCT LEFT(email, 100)) / COUNT(*)
为什么VARCHAR(255)会触发行溢出
InnoDB的ROW_FORMAT=COMPACT(默认)下,单行记录超过约8KB就会启用off-page存储,把长字段值移出主页,只留20字节指针。而VARCHAR(255)在utf8mb4下理论最大1020字节,看似安全——但这是单列。一旦表里还有几个类似字段,或加了TEXT,叠加起来很容易越界。
真实影响:
- 哪怕只查
id和一个VARCHAR(255)字段,如果该行已溢出,就得额外一次I/O读取外部页 -
ALTER TABLE ... MODIFY COLUMN从VARCHAR(256)改回VARCHAR(255)会强制重建全表——因为长度前缀从2字节变1字节,结构不兼容 - 备份工具(如
mydumper)对溢出行处理更慢,恢复时也更容易卡住
怎么定长度才不靠猜
别复制模板,直接查数据。最有效的做法就三步,且必须按顺序来:
- 跑
SELECT MAX(LENGTH(col)) FROM table,注意是LENGTH不是CHAR_LENGTH(要算字节数) - 看分布:
SELECT COUNT(*) FROM table WHERE LENGTH(col) > 200,如果结果为0,VARCHAR(255)就是过度设计 - 结合用途选类型:URL超200字符、富文本摘要、JSON片段,一律改用
TEXT;固定格式如手机号、国家码,用VARCHAR(15)或CHAR(2)
最容易被忽略的是:ALTER操作本身会锁表,而长度误设带来的性能损耗是持续性的——宁可多花10分钟查数据,也不要图快写个255完事。


















