最直覺的隨機抽取寫法是 ORDER BY RAND() LIMIT n,但 MySQL 官方文件早已警告:RAND() 在 ORDER BY 中會被多次評估,導致全表掃描與臨時表排序。實測 15 萬筆資料抽 5 筆耗時 8 秒以上,完全無法用於生產環境。
網路上主流做法是先取得 MAX(id),再乘以 RAND() 產生隨機起點:
SELECT * FROM `table` AS t1
JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `table`)) AS id) AS t2
WHERE t1.id >= t2.id
ORDER BY t1.id ASC LIMIT 5;
缺點:會產生連續 5 筆資料,非真正隨機。若需真正隨機,只能重複執行 5 次 LIMIT 1。
若主鍵不從 1 開始,或有刪除導致空洞,直接用 MAX(id) * RAND() 會讓前段資料被抽中機率過高。正確公式:
SELECT * FROM `table`
WHERE id >= (
SELECT FLOOR(RAND() * ((SELECT MAX(id) FROM `table`) - (SELECT MIN(id) FROM `table`)) + (SELECT MIN(id) FROM `table`))
)
ORDER BY id LIMIT 1;
SELECT * FROM `table` AS t1
JOIN (
SELECT ROUND(RAND() * ((SELECT MAX(id) FROM `table`) - (SELECT MIN(id) FROM `table`)) + (SELECT MIN(id) FROM `table`)) AS id
) AS t2
WHERE t1.id >= t2.id
ORDER BY t1.id LIMIT 1;
| 寫法 | 總耗時 | 平均單次 |
|---|---|---|
| WHERE 子查詢 | 0.147433 秒 | 0.0147 秒 |
| JOIN 子查詢 | 0.015130 秒 | 0.0015 秒 |
JOIN 寫法快近 10 倍,主因是 MySQL 能更有效利用索引與暫存表機制。
-- 抽取 1 筆真正隨機資料(含邊界修正)
SELECT * FROM `table` AS t1
JOIN (
SELECT ROUND(
RAND() * (
(SELECT MAX(id) FROM `table`) - (SELECT MIN(id) FROM `table`)
) + (SELECT MIN(id) FROM `table`)
) AS id
) AS t2 ON t1.id >= t2.id
ORDER BY t1.id LIMIT 1;
-- 抽取 N 筆不重複隨機資料(應用層迴圈呼叫 N 次,或用 UNION ALL)
-- 注意:單次查詢無法保證 N 筆不重複且高效,建議應用層去重。
ORDER BY RAND() 即可)、需嚴格統計學隨機分佈(建議應用層抽樣)。摒棄 ORDER BY RAND(),善用 MIN/MAX(id) 邊界配合 JOIN,可將隨機抽取效能提升 10~100 倍。生產環境務必實測自身資料分佈,選擇最適合的寫法。