ORDER BY RAND() 是 MySQL 最著名的效能殺手:每行計算隨機數、建立臨時表、文件排序,百萬級資料秒級變分鐘級。本文通過實測對比 7 種寫法,揭示將 RAND() 下推至 JOIN 子查詢的核心優化模式,助您實現毫秒級隨機抽樣。
t_innodb_random:39 萬行,id 無序分佈,建立 idx_id 索引。SELECT id FROM t WHERE id = 13412 → 0.00 秒 (const ref)。| # | SQL 寫法 | 執行計劃關鍵字 | 耗時 | 評價 |
|---|---|---|---|---|
| 1 | WHERE id = ROUND(RAND()*13241324) | type=index (全索引掃描) | 0.26 秒 | ✗ RAND() 視為變量,不可用索引查找 |
| 2 | WHERE id = ROUND(RAND()*(SELECT MAX(id) FROM t)) | type=index + 子查詢優化 | 0.27 秒 | ✗ 同樣全掃描 |
| 3 | 普通子查詢 WHERE id = (SELECT ROUND(RAND()*MAX(id)) FROM t) | type=index | 0.27 秒 | ✗ 無改善 |
| 4 | JOIN 衍生表 (常量)JOIN (SELECT ROUND(RAND()*13241324) AS id2) t2 ON t1.id=t2.id2 | type=ref (const) | 0.00 秒 | ✓ 最佳:衍生表產生常量,走索引精確查找 |
| 5 | 範圍 + LIMIT 1WHERE id > (SELECT ROUND(RAND()*MAX(id)) FROM t) LIMIT 1 | type=index + LIMIT | 0.00 秒 | ✓ 掃描至首匹配即停止,極快 |
| 6 | 多行 ORDER BY RAND() LIMIT 1000 | Using temporary; Using filesort | 0.41 秒 | ✗ 經典慢查詢 |
| 7 | JOIN 衍生表 (範圍)JOIN (SELECT RAND()*MAX(id) AS nid FROM t) t2 ON t1.id > t2.nid LIMIT 1000 | type=range | 0.00 秒 | ✓ 多行隨機抽樣最優解 |
SELECT t1.id FROM t_innodb_random t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM t_innodb_random)) AS rand_id) t2 ON t1.id = t2.rand_id;
衍生表 t2 僅 1 行,優化器視為 system 表,主查詢走 ref 精確查找。
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;
衍生表產生隨機閾值,主查詢走 range 掃描索引,LIMIT 截斷,避免臨時表與 filesort。
將任意 SELECT ... ORDER BY RAND() LIMIT n 重寫為:
SELECT 欄位 FROM 目標表 t1 JOIN (SELECT RAND() * (SELECT MAX(主鍵) FROM 目標表) AS nid) t2 ON t1.主鍵 > t2.nid LIMIT n;
前提:主鍵為整數自增或均勻分佈;若主鍵稀疏/非整數,需改用 ROW_NUMBER() 窗口函數 (8.0+) 或映射表。
ON t1.id >= t2.nid 或雙閾值 BETWEEN。INDEX(target_col, pk),同上模式套用。SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY RAND()) rn FROM t) x WHERE rn <= n 仍需全表排序,不如 JOIN 模式快。SELECT MAX(id) 再 RAND() 生成 ID 列表批量 IN 查詢,完全繞過資料庫 RAND() 開銷。RAND() 優化本質是「將非確定性計算下推至衍生表,轉化為確定性常量/範圍,利用索引精確/範圍查找」。掌握 JOIN 衍生表 + LIMIT 兩大招式,即可將隨機查詢從「全表掃描 + 臨時表 + 排序」降維為「索引範圍掃描 + 早期終止」,效能提升 2-3 個數量級。