mysql临时表排序按varchar定义长度而非实际内容预分配内存,导致utf8mb4下varchar(1000)单行占4000字节,易触发内存溢出并降级磁盘表;varchar(255)比varchar(50)内存开销大5倍,加剧性能下降;alter table改长需重写表,改短仍需全表校验;关键参数为tmp_table_size、max_heap_table_size和sort_buffer_size,且须依据字段95分位长度+余量合理设长。

MySQL临时表排序为什么会用到VARCHAR长度
当查询涉及 ORDER BY、GROUP BY 或 DISTINCT 且无法走索引时,MySQL 可能会创建内存临时表(MEMORY 引擎)来完成排序。这时,它会按字段定义的**最大可能长度**预分配内存空间,而不是按实际内容长度。
比如一个 VARCHAR(1000) 字段,即使当前所有值都只有 5 个字符,MySQL 在内存临时表中仍会为每一行预留最多 1000 字符的空间(考虑字符集,utf8mb4 下是 4000 字节),极易触发 Memory engine out of memory,进而自动降级为磁盘临时表(MyISAM 或 InnoDB),性能断崖式下降。
为什么255不是万能解,反而可能更危险
很多人习惯把所有字符串字段设成 VARCHAR(255),觉得“反正不占额外空间”。但在排序场景下,这个习惯恰恰放大了内存开销:
-
VARCHAR(255)在 utf8mb4 下,单字段最大需预留 255 × 4 = 1020 字节/行;10 万行就超 100MB 内存,远超默认tmp_table_size和max_heap_table_size(通常各为 16MB) - 而
VARCHAR(50)同样 utf8mb4 下仅需 200 字节/行,10 万行才 20MB,大概率留在内存里 - 更关键的是:InnoDB 表的排序缓冲区(
sort_buffer_size)也受字段定义长度影响——过长的VARCHAR会导致单次排序能处理的行数急剧减少,被迫多次归并,CPU 和 I/O 开销双升
ALTER TABLE改长度时的隐性成本差异
修改 VARCHAR 长度不是“无感操作”,尤其在大表上,不同方向的变更代价完全不同:
- 从
VARCHAR(20)→VARCHAR(50):InnoDB 可以只改元数据(instant DDL),毫秒级完成 - 从
VARCHAR(50)→VARCHAR(100):若原列已存在大量非空数据,InnoDB 必须重写整张表(copy DDL),耗时与数据量正相关——你看到的“4.8 秒改百万行”就是典型表现 - 从
VARCHAR(100)→VARCHAR(50):看似收缩,但 MySQL 仍需逐行校验内容是否超长,同样触发全表扫描,不可轻视
真正该盯住的三个硬指标
别只盯着“会不会截断”,排序场景下,这三个参数才是命门:
-
tmp_table_size和max_heap_table_size:两者取小值,决定内存临时表上限。建议设为一致,且不低于业务峰值排序所需估算值 -
sort_buffer_size:每个排序线程独占,不是全局总和。设太大易 OOM,太小则频繁归并。建议单值 ≤ 4MB,配合合理VARCHAR长度使用 - 字段实际 95 分位长度:用
SELECT ROUND(AVG(LENGTH(col)), 0), MAX(LENGTH(col)) FROM t查真实分布,再向上取整留 10%~20% 余量,比拍脑袋设 255 或 1000 更可靠
最常被忽略的一点:utf8mb4 下,一个汉字占 4 字节,但排序时内存分配按“定义长度 × 字节因子”算,和字符语义无关。哪怕你存的全是 ASCII,只要定义了 VARCHAR(1000),它就按 4000 字节/行算——这点在做容量评估时几乎没人 check。











