order by rand() 慢是因为每行都调用 rand() 并全表排序,无法利用索引,数据量大时触发 filesort 导致 i/o 和 cpu 飙升;推荐用主键范围随机抽样(如 where id >= ceil(rand() * max(id)) order by id limit n)替代。

ORDER BY RAND() 为什么慢到不能用
因为 MySQL 在执行 ORDER BY RAND() 时,会对表中**每一行都调用一次 RAND() 函数**,再对所有行做完整排序——哪怕你只想要 1 条记录。数据量稍大(比如 10 万行以上),就会触发 filesort,磁盘 I/O 和 CPU 消耗陡增,查询可能从几毫秒飙到数秒。
常见错误现象:SELECT * FROM user ORDER BY RAND() LIMIT 10 在百万级表上执行超时、拖垮整个数据库连接池。
- 不是“随机”本身慢,是“全表打乱再截取”这个逻辑不可扩展
- 即使加了主键索引,
ORDER BY RAND()也基本无法利用索引 - MyISAM 和 InnoDB 表表现一致,不存在引擎差异优化空间
用主键范围随机抽样(推荐:简单且高效)
前提是表有自增主键(id),且无大量删除导致空洞。思路是:先估算主键范围,生成随机 ID,用 WHERE id >= ? ORDER BY id LIMIT N 取值,失败则重试几次。
SELECT * FROM user WHERE id >= CEIL(RAND() * (SELECT MAX(id) FROM user)) ORDER BY id LIMIT 10;
说明:这避免了全表扫描和排序,只走主键索引 B+ 树查找。但要注意:
- 如果
id不连续(比如删过大量记录),可能查不到足够行数,需在应用层补足(例如最多重试 3 次) -
MAX(id)是轻量查询,可缓存;不要写成(SELECT MAX(id) FROM user) * RAND()—— 这会导致子查询被反复执行 - 若主键非数字或不单调(如 UUID),此法失效,需换方案
用 JOIN 随机关联(适合中等规模、ID 稀疏场景)
当主键空洞较多,又不想改应用逻辑时,可以用两次随机采样 + JOIN 规避空洞问题:
SELECT t1.* FROM user AS t1 JOIN (SELECT CEIL(RAND() * (SELECT MAX(id) FROM user)) AS id) AS t2 ON t1.id >= t2.id ORDER BY t1.id LIMIT 10;
它比纯 ORDER BY RAND() 快一个数量级,但仍有小概率返回少于 10 行。实际使用建议:
- 把
MAX(id)提前查出,在应用里生成多个随机起点,分别查再合并去重 - 慎用于高并发场景:每个查询仍要读一次
MAX(id),可考虑用 Redis 缓存该值(每分钟更新一次) - 不要在子查询里嵌套
RAND()多次,MySQL 可能每次生成不同值,导致逻辑错乱
真正需要“均匀随机”时的取舍点
如果你的业务要求严格均匀(比如抽奖系统),那必须接受代价:要么用 ORDER BY RAND() 加分页预热(仅限几千行以内),要么导出 ID 列表到应用层用 Fisher–Yates 洗牌——数据库只负责高效批量读取。
容易被忽略的一点:没有银弹。所谓“优化”,本质是在“随机性质量”“响应时间”“实现复杂度”三者间做选择。线上表超过 50 万行还坚持用 ORDER BY RAND(),通常不是技术问题,而是需求没对齐。











