ORDER BY RAND() 慢是因为每行都调用 RAND() 并全量排序,导致 I/O 和内存开销剧增;应改用主键范围随机、时间戳/UUID 采样或应用层缓存 ID 等替代方案。

ORDER BY RAND() 为什么慢得离谱
因为 MySQL 在执行 ORDER BY RAND() 时,会对**每一行都调用一次 RAND() 函数**,再把结果全加载进临时表排序——数据量一过万,I/O 和内存开销就指数级上涨。不是语法错,是执行模型天生不适合大数据集。
常见错误现象:SELECT * FROM users ORDER BY RAND() LIMIT 10 在百万级表上跑几秒甚至超时;EXPLAIN 显示 Using temporary; Using filesort。
- 只在小表(ORDER BY RAND(),别心存侥幸
-
RAND()每次调用都重新生成,无法复用,所以没法加索引优化 - 如果只是取 1 条随机记录,
ORDER BY RAND() LIMIT 1依然要扫全表,没本质改善
用主键范围抽样替代全表 RAND 排序
核心思路:避开排序,靠主键(最好是自增整型)的已知范围做两次随机 + 范围查询,实测提速几十倍。
使用场景:主键连续或基本连续、无大量删除碎片、需要少量随机行(如 1–100 条)。
示例(取 1 条):
SELECT * FROM users WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM users))) ORDER BY id LIMIT 1;
- 先查
MAX(id)得到上限,用RAND()生成一个随机起点 -
WHERE id >= ...利用主键索引快速定位,避免全表扫描 - 但若 id 空洞太多(比如删了大量中间记录),可能查不到结果——需加兜底重试逻辑
- 取多条时,建议生成多个不重复随机 id 后用
IN查询,比反复执行更稳
用 UUID 或时间戳字段做伪随机过滤(适合非主键场景)
当没有合适数字主键,但有 created_at 或 uuid 字段时,可借其分布特性近似随机。
例如按时间戳秒级哈希过滤:
SELECT * FROM logs WHERE MOD(UNIX_TIMESTAMP(created_at), 100) = FLOOR(RAND() * 100) LIMIT 5;
-
MOD(UNIX_TIMESTAMP(...), N)把时间打散成 0–N−1 的桶,再随机选一个桶 - 不保证绝对均匀,但比
ORDER BY RAND()快两个数量级,且能走created_at索引 - UUID 场景可用
LEFT(uuid, 4)+CONV(..., 16, 10)转成数字后取模,但注意前缀重复率 - 该方法本质是「采样」,不是严格随机,业务能接受偏差才用
真正需要严格随机且数据量大?别硬扛,换方案
MySQL 不是随机数引擎。硬要在千万级表上做严格随机排序,等于让数据库干它不擅长的事。
- 应用层缓存全量 ID 列表(Redis),用
SRANDMEMBER或本地 shuffle 后分页查详情 - 写入时给每条记录加
random_score DOUBLE DEFAULT RAND()字段,建索引,查时WHERE random_score > RAND() ORDER BY random_score LIMIT N - 定期用事件调度器刷新
random_score,避免倾斜——这步容易被忽略,导致长期不更新后随机性退化
最麻烦的不是写法,是意识到:RAND() 是玩具函数,不是生产工具。越早放弃“一条 SQL 解决”的执念,越少掉坑里。











