ORDER BY RAND() LIMIT N 是 MySQL 著名的性能殺手:全表掃描 + 臨時表 + filesort。本文通過 7 組對比實驗(含 EXPLAIN、耗時),系統展示如何將隨機抽樣從 0.41 秒優化至 0.00 秒,核心技巧是將 RAND() 下推至 JOIN 衍生表,轉為常量等值/範圍查詢。
t_innodb_random:39 萬行,id INT UNSIGNED、user VARCHAR(64)、KEY idx_id(id),無顯式主鍵(隱式聚簇索引 GEN_CLUST_INDEX)。| # | SQL 模式 | EXPLAIN 關鍵特徵 | 耗時 | 評價 |
|---|---|---|---|---|
| 1 | 常量等值 WHERE id=13412 | type=ref, key=idx_id, rows=1 | 0.00s | ✅ 基準最優 |
| 2 | WHERE id = ROUND(RAND()*const) | type=index, rows=393k, Using where | 0.26s | ❌ 全索引掃描 |
| 3 | WHERE id = ROUND(RAND()*(SELECT MAX(id)...)) | 同 #2,含 SUBQUERY | 0.27s | ❌ 無改善 |
| 4 | 普通子查詢 WHERE id = (SELECT ROUND(RAND()*(SELECT MAX(id)...))) | 同 #2 | 0.27s | ❌ |
| 5 | JOIN 衍生表(常量)JOIN (SELECT ROUND(RAND()*13241324) AS id2) t2 ON t1.id=t2.id2 | t2: system; t1: ref, key=idx_id, rows=1 | 0.00s | ✅ 完美等值查詢 |
| 6 | 範圍 + LIMIT 1WHERE id > (SELECT ROUND(RAND()*(SELECT MAX(id)...))) LIMIT 1 | type=index, rows=393k 但 LIMIT 1 早停 | 0.00s | ✅ 實用折中 |
| 7 | JOIN 衍生表 + 範圍 + LIMIT NJOIN (SELECT RAND()*(SELECT MAX(id)...) AS nid) t2 ON t1.id > t2.nid LIMIT 1000 | t2: system; t1: range, key=idx_id, rows=196k | 0.00s | ✅ 批量隨機抽樣最優 |
-- 慢:全表賦值隨機數 -> 排序 -> 取前 N
SELECT id FROM t ORDER BY RAND() LIMIT 1000;
-- 快:衍生表產生 1 個隨機常量 -> 索引範圍/等值查找 -> 直接取 N 行
SELECT t1.id
FROM t_innodb_random t1
JOIN (SELECT RAND() * (SELECT MAX(id) FROM t_innodb_random) AS nid) t2
ON t1.id > t2.nid
LIMIT 1000;
RAND() 在衍生表中僅執行 1 次,結果作為常量參與 JOIN ON 條件。t1.id > const 可走 range 訪問,直接定位葉子頁,無臨時表、無 filesort。SELECT t1.*
FROM t t1
JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM t)) AS nid) t2
ON t1.id = t2.nid;
SELECT t1.*
FROM t t1
JOIN (SELECT RAND() * (SELECT MAX(id) FROM t) AS nid) t2
ON t1.id > t2.nid
LIMIT 1;
SELECT t1.*
FROM t t1
JOIN (SELECT RAND() * (SELECT MAX(id) FROM t) AS nid) t2
ON t1.id > t2.nid
LIMIT N;
SELECT t1.id
FROM t t1
JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM t)) AS nid FROM t LIMIT N) t2
ON t1.id = t2.nid;
>)仍正確,等值法(=)可能返回空集。ORDER BY RAND() 反而更簡單,優化收益微乎其微。id 回表,或直接 SELECT * 視業務而定。MAX(id),可考慮應用層緩存最大 ID 或定時任務維護。結語:MySQL 隨機抽樣的黃金法則——「把 RAND() 鎖在衍生表裡,讓優化器看到常量」。將 ORDER BY RAND() LIMIT N 改寫為 JOIN (SELECT RAND()*MAX(id)...) ON id > nid LIMIT N,即可在保證業務語義的前提下,獲得百倍性能提升。