業務常需從大表隨機抽取 N 條記錄。最直觀的 ORDER BY RAND() LIMIT 5 在萬級數據下耗時 80ms,百萬級更是災難。本文演進三種方案,最終落地高效 JOIN 寫法。
| 方案 | 8千條測試耗時 | MySQL 版本要求 | 核心缺陷 |
|---|---|---|---|
| ORDER BY RAND() LIMIT N | 0.08 秒 | 所有版本 | 全表掃描 + 文件排序,O(N log N) |
| JOIN + 子查詢 (MAX(id) 法) | 0.02 秒 | 4.1+ | 假設 ID 連續無空洞,分布不均導致邊界值概率偏高 |
| JOIN + 子查詢 (MIN/MAX 範圍法) | ~0.02 秒 | 4.1+ | 兼容非連續 ID,分布更均勻 |
SELECT * FROM table_name ORDER BY RAND() LIMIT 5;
官方文檔警告:ORDER BY 會重複計算 RAND(),導致性能指數級下降。僅適用於小表或一次性臨時查詢。
SELECT * FROM table_name AS r1
JOIN (
SELECT ROUND(RAND() * (SELECT MAX(id) FROM table_name)) AS id
) AS r2 ON r1.id >= r2.id
ORDER BY r1.id ASC LIMIT 5;
原理:在 [1, MAX(id)] 取隨機點,向後取 N 條。缺陷:ID 有空洞或刪除導致不連續時,大 ID 間隔的記錄被選中概率顯著偏高。
SELECT t1.id FROM zhyx_items AS t1
JOIN (
SELECT ROUND(
RAND() * (
(SELECT MAX(id) FROM zhyx_items) - (SELECT MIN(id) FROM zhyx_items)
) + (SELECT MIN(id) FROM zhyx_items)
) AS id
) AS t2 ON t1.id >= t2.id
ORDER BY t1.id LIMIT 0,5;
優勢:在 [MIN(id), MAX(id)] 範圍均勻取隨機點,兼容自增 ID 的空洞與非連續分布,概率分布更公平。
id 列有索引(主鍵自帶),JOIN 只掃描索引不回表IN 查詢或 UNION ALL 合併TABLESAMPLE(MySQL 8.0+ 不支持,需應用層實現)RAND(),會導致每行重複計算子查詢 JOIN 寫法需要 MySQL 4.1+。舊版本只能退回應用層兩次查詢:先 SELECT COUNT(*),再 LIMIT offset, N。