mysql千万级排序慢的本质是磁盘临时文件引发大量随机i/o,而非cpu或sql问题;当sort_buffer_size不足时,数据溢出到磁盘生成mysqld-*.tmp文件,导致iostat显示%util接近100%。

MySQL千万级排序为什么慢?本质是磁盘临时文件拖垮性能
当 ORDER BY 涉及千万行且无法走索引时,MySQL 会把待排序数据加载进内存做快速排序;一旦超出 sort_buffer_size,就会写入磁盘生成临时归并文件(mysqld-*.TMP),触发大量随机 I/O —— 这才是排序变慢的根因,不是 CPU 或 SQL 写法问题。
常见现象:SHOW PROCESSLIST 显示状态为 Sorting result,同时 iostat -x 1 观察到 %util 长期接近 100%,tmpdir 目录下出现多个大体积 .TMP 文件。
怎么调 sort_buffer_size 才有效?不是越大越好
该参数是**每个连接独占**的内存缓冲区,设太高会导致:并发连接多时内存爆炸、触发系统 OOM Killer 杀掉 mysqld;设太低则频繁落盘。关键看单次排序的数据量和并发压力。
- 先估算单次排序所需内存:假设排序字段是
INT(4 字节)+VARCHAR(50)(平均存 20 字节)+ 行指针(约 6 字节),单行约 30 字节;千万行 ≈ 300MB —— 这远超默认的 256KB,必须调大 - 生产建议值:从
4M起步,逐步加到16M~32M,但需满足:sort_buffer_size × max_connections ≤ 总内存 × 0.25 - 动态生效命令:
SET SESSION sort_buffer_size = 16777216;(仅当前连接),或写入配置文件my.cnf的[mysqld]段后重启 - 注意:全局设置
SET GLOBAL不影响已存在的连接,只对新连接生效
比调参更关键的三件事:绕过排序、缩小数据集、用对索引
单纯堆 sort_buffer_size 是治标。真正千万级场景必须组合优化:
- 确认
ORDER BY字段是否有可用索引:用EXPLAIN看type是否为index或range,且key显示用了哪个索引;复合索引要遵循最左前缀,比如ORDER BY a,b需要(a,b)索引,(b,a)无效 - 用
LIMIT提前截断:如果只取 Top N,务必加上LIMIT N,MySQL 会启用优先队列(priority queue)算法,内存占用恒定 O(N),不随总行数增长 - 避免
SELECT *排序:只查必要字段,减少单行内存占用;尤其避开TEXT/BLOB字段,它们不存于排序缓冲区,但会引发回表,放大 I/O
监控是否真避开了磁盘排序?看这两个指标
调完参数后不能只看查询变快,要验证是否真的没落盘:
- 执行排序语句后,立即查:
SHOW STATUS LIKE 'Sort_merge_passes';—— 该值为0才表示全程内存排序;若 > 0,说明仍发生磁盘归并 - 观察
SHOW STATUS LIKE 'Sort_scan';和Sort_range:前者是全表扫描后排序,后者是索引扫描后排序;后者更优,但两者都可能落盘,仍要结合Sort_merge_passes判断 - 临时目录磁盘空间必须预留充足:即使调大
sort_buffer_size,极端情况(如并发高 + 数据倾斜)仍可能生成 TMP 文件,tmpdir所在分区至少留 20% 空闲空间
真正难的是权衡:索引能加速排序,但写入代价高;sort_buffer_size 能压低落盘概率,但吃内存;没有银弹,得按查询模式、硬件资源、QPS 压力一起算账。











