order by rand() 会让 mysql cpu 打满,因为其需为每一行调用 rand() 并全量排序,触发全表扫描、内存排序及临时表落盘,导致 sort_merge_passes 暴涨和 mysqld cpu 长期超 90%。

ORDER BY RAND() 为什么让 MySQL CPU 打满
因为 MySQL 必须为表中**每一行都调用一次 RAND() 函数**,再对全部 N 行做完整排序——哪怕你只写 LIMIT 1。这不是“随机取一条”,而是“全表打乱再截头”,时间复杂度是 O(N log N),且无法利用任何索引。
常见现象包括:SHOW PROCESSLIST 中大量连接卡在 Sorting result 或 Creating sort index;慢日志里反复出现同一句 ORDER BY RAND();监控看到 Sort_merge_passes 暴涨、mysqld 进程 CPU 占用长期 90%+。
MySQL 5.7 下 ORDER BY RAND() 的执行陷阱
MySQL 5.7 不支持对 RAND() 做物化或下推优化,所有计算都在 server 层完成。它会:
- 全表扫描(
type: ALL),哪怕主键索引存在也完全无视 - 为每行分配内存存随机值,触发
sort_buffer_size频繁扩容 - 若结果集超出内存阈值,自动落盘生成 MyISAM 临时表,伴随
Copying to tmp table on disk -
RAND()是非确定性函数,导致查询无法被 Query Cache 缓存(如果还开着)
为什么 Java 应用调用时更危险
Java 侧通常用 JDBC 同步执行 SQL,若没设 queryTimeout 或连接池熔断策略,一条 ORDER BY RAND() 就可能:
- 独占一个数据库连接数秒甚至几十秒,阻塞后续请求
- 并发 5–10 QPS 时,CPU 和连接池瞬间耗尽,引发雪崩
- 日志里反复出现
converting HEAP to MyISAM,说明已进入恶性循环
真正有效的替代方案(不是“差不多快”,而是毫秒级)
核心原则:**把随机逻辑从 MySQL 移到应用层,SQL 只走主键索引查找**。前提是表有自增 id 且空洞不严重:
- 先查总数:
SELECT COUNT(*) FROM user(可缓存 60 秒) - Java 生成随机偏移:
int offset = ThreadLocalRandom.current().nextInt(count) - 执行索引查询:
SELECT * FROM user WHERE id >= ? ORDER BY id LIMIT 1(? 是估算的起始id) - 若返回空,最多重试 3 次;需要多条时,生成多个随机
id并去重
空洞严重?预加载有效 id 到 Redis,用 SRANDMEMBER 或 SPOP —— 这才是生产环境真正扛得住的随机。
别信“加个索引就能优化 ORDER BY RAND()”,它根本不会走索引。真正难的是把“随机”这个需求,从 SQL 层剥离出来,而不是在原地打补丁。











