<p>ORDER BY RAND()在百万级大表中极慢甚至卡死,因其需全表扫描、每行计算随机数并文件排序;推荐用主键范围随机采样(如WHERE id >= FLOOR(RAND() * (MAX(id)-MIN(id)) + MIN(id)) ORDER BY id LIMIT 10),需确保id有索引且空洞率低,否则需重试或改用预生成ID池等方案。</p>

对大表(比如百万级以上)直接用 ORDER BY RAND() LIMIT 10 会严重拖慢查询,甚至卡死;小表(
为什么 ORDER BY RAND() 在大表上很慢?
MySQL 执行 ORDER BY RAND() 时,会对**每一行都调用一次 RAND() 函数**,再把整张表按这些随机值排序——相当于全表扫描 + 全表排序。即使你只想要 10 条,引擎也得生成并比较全部行的随机数。
- 执行计划里常看到
type: ALL和巨大的rows值 - 并发稍高时,CPU 和临时磁盘 I/O 会飙升
- 如果表没主键或
id不连续,JOIN类优化方案也会失效
用主键区间采样:适用于 id 连续或近似连续的表
核心思路是:先算出 id 的范围,生成一个随机起点,再向后取够 10 条。不排序、不全表扫,靠索引快速定位。
SELECT * FROM your_table WHERE id >= FLOOR(RAND() * (SELECT MAX(id) - MIN(id) FROM your_table) + (SELECT MIN(id) FROM your_table)) ORDER BY id LIMIT 10;
- 必须确保
your_table.id是主键或有索引,否则WHERE id >= ...无法走索引 - 如果
id空洞太多(比如删过大量数据),可能查不到 10 条,需配合重试逻辑 -
FLOOR()比CEIL()更稳妥,避免越界
带条件筛选时怎么保持随机性?
加了 WHERE 条件后,ORDER BY RAND() 依然会先过滤再随机排序,性能瓶颈照旧。更稳的做法是:先用子查询估算符合条件的总行数,再用类似主键采样的方式构造偏移量。
SELECT * FROM your_table
WHERE status = 1
AND id >= (
SELECT FLOOR(RAND() * (MAX(id) - MIN(id))) + MIN(id)
FROM your_table WHERE status = 1
)
ORDER BY id
LIMIT 10;- 子查询里的
WHERE status = 1必须和外层一致,否则范围错位 - 如果条件字段(如
status)没有索引,子查询本身就会变慢,得先加索引 - 这种写法不能保证“绝对均匀随机”,但对大多数业务场景(比如首页推荐抽样)已足够
真正需要强随机性且数据量极大时,别硬扛 SQL
当要求每条记录被选中的概率严格相等、且表超过 500 万行时,纯 SQL 很难兼顾效率与公平性。这时候该换思路:
- 在应用层维护一个轻量缓存表,定期用
ORDER BY RAND() LIMIT 1000预抽一批 ID 存起来,业务查缓存 - 用
pt-online-schema-change类工具给大表加个rand_hash列,值为CRC32(CONCAT(id, UNIX_TIMESTAMP())),然后WHERE rand_hash % 1000 < 10快速筛出约 1% 样本再LIMIT 10 - 接受“伪随机”:用
id % N = RAND() % N这类模运算做分桶,适合离线分析场景
最常被忽略的一点:没有银弹。ORDER BY RAND() 看似简单,但它在 explain 里暴露的 rows 值,就是你线上告警的伏笔。先看数据分布,再选策略,比背公式重要得多。



















