MySQL 中 ORDER BY RAND() 或 WHERE ... = RAND() 會導致全表掃描、臨時表排序,效能極差。本文通過 7 組實測對比,證明將 RAND() 移入 JOIN 子查詢可將隨機查詢從秒級優化至毫秒級。
t_innodb_random:393,216 行,id INT PRIMARY KEY(無顯式自增),idx_id 索引。id 亂序分佈。| # | 查詢模式 | 執行計劃關鍵 | 耗時 |
|---|---|---|---|
| 1 | 常量等值 WHERE id = 13412 | ref / Using index | 0.00 秒 |
| 2 | WHERE id = ROUND(RAND()*13241324) | index / 全索引掃描 | 0.26 秒 |
| 3 | WHERE id = ROUND(RAND()*(SELECT MAX(id)...)) | index / 全索引掃描 | 0.27 秒 |
| 4 | 子查詢 WHERE id = (SELECT ROUND(RAND()*(SELECT MAX(id)...))) | index / 全索引掃描 | 0.27 秒 |
| 5 | JOIN 子查詢(常量 RAND) JOIN (SELECT ROUND(RAND()*13241324) AS id2) t2 ON t1.id = t2.id2 | ref / const / 0.00 秒 | 0.00 秒 |
| 6 | WHERE id > (SELECT RAND()*(SELECT MAX(id)...)) LIMIT 1000 | index / LIMIT 1000 終止 | 0.04 秒 |
| 7 | JOIN + RAND 子查詢 + LIMIT 1000 JOIN (SELECT RAND()*(SELECT MAX(id)...) AS nid) t2 ON t1.id > t2.nid LIMIT 1000 | range / 0.00 秒 | 0.00 秒 |
| 8 | ORDER BY RAND() LIMIT 1000 | index / Using temporary; Using filesort | 0.41 秒 |
type=system),外層走 ref 或 range。SELECT id FROM `table` t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `table`)) AS nid) t2 ON t1.id = t2.nid;
SELECT id FROM `table` t1 JOIN (SELECT RAND() * (SELECT MAX(id) FROM `table`) AS nid) t2 ON t1.id > t2.nid LIMIT N;
SELECT id FROM `table` t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `table`)) AS nid FROM `table` LIMIT N) t2 ON t1.id = t2.nid;
RAND() 在同一查詢中多次呼叫會重新計算,衍生表確保只算一次。MAX(id) 子查詢會被優化器優化為常量(Select tables optimized away),無額外開銷。id > nid)可能取不到精確 N 筆,可接受近似隨機;需精確則用等值法配合多隨機點。掌握此模式,隨機抽樣不再是效能殺手。