mysql临时表按varchar定义长度而非实际内容预分配内存,因执行引擎需预留最大空间以支持o(1)随机访问;utf8mb4下varchar(255)单行占1020字节,远超varchar(50)的200字节,易触发磁盘落盘导致性能断崖下降。

MySQL临时表为什么按定义长度预分配内存
因为执行引擎在内存中无法动态压缩VARCHAR字段——它必须为每行预留最大可能空间,才能实现O(1)随机访问。哪怕你存的是"a",VARCHAR(255)在utf8mb4下仍按255×4=1020字节算;而VARCHAR(50)只占50×4=200字节。这不是bug,是设计取舍:用空间换排序/查找速度。
临时表“膨胀”会直接触发磁盘落盘
当单行预估内存过大,sort_buffer或临时表总大小很容易突破tmp_table_size和max_heap_table_size的较小值。一旦超限,MySQL立刻把内存表转成磁盘表(InnoDB或MyISAM),I/O陡增,查询变慢几倍甚至几十倍。
-
EXPLAIN里看到Using temporary; Using filesort不致命,但若SHOW STATUS LIKE 'Created_tmp_disk_tables'持续增长,就是内存被撑爆了 - 查
SELECT @@tmp_table_size, @@max_heap_table_size,两个值必须完全一致,否则按小的那个生效 - 1万行数据,
VARCHAR(255)比VARCHAR(50)多占约8MB内存 —— 这还没算其他字段和并发连接
不同存储引擎对VARCHAR长度的敏感度差异极大
InnoDB磁盘存储确实“省”,VARCHAR(255)和VARCHAR(100)存"abc"物理开销几乎一样;但MyISAM不是——它在.MYD文件里真按定义长度占位,VARCHAR(255)存一个字符也占256字节(255+1长度标识)。所以旧系统里盲目设255,表体积可能翻倍。
- MyISAM改长度必须
ALTER TABLE MODIFY COLUMN,且OPTIMIZE TABLE才能回收空间 - InnoDB虽不涨磁盘,但
Row_format=COMPACT下,只要定义长度>255,就强制用2字节存长度头——哪怕你ALTER时只从256缩到255,也会触发全表重建 - MEMORY引擎更狠:所有VARCHAR都被当定长CHAR处理,浪费100%内存
怎么验证你的VARCHAR长度是否真有问题
别猜,直接查数据分布和执行行为。很多团队直到线上抖动才意识到问题,其实三步就能定位:
- 查真实最大长度:
SELECT MAX(LENGTH(email)) FROM users;,再加20%余量就是合理上限 - 看前缀区分度:
SELECT COUNT(DISTINCT LEFT(email, 60)) / COUNT(*) AS selectivity FROM users;,>0.95说明60就够 - 盯住
SHOW GLOBAL STATUS LIKE 'Created_tmp%',尤其Created_tmp_disk_tables / Created_tmp_tables比值>20%就得动手
真正容易被忽略的不是“该不该设255”,而是没人定期跑EXPLAIN FORMAT=TREE看是否用了内存表,也没人检查max_length_for_sort_data是否被长字段拖垮——这些指标一飙高,VARCHAR长度问题就从设计隐患变成线上抖动根源。











