需求:從千萬級新聞表隨機抽取 10 條資料。ORDER BY RAND() LIMIT 10 全表掃描排序效能極差。本文提供兩種高效 SQL 寫法,利用主鍵索引範圍查詢,毫秒級完成。
SELECT * FROM `news`
WHERE id >= (
SELECT FLOOR(RAND() * (SELECT MAX(id) FROM `news`))
)
LIMIT 10;
原理:子查詢產生一個隨機 ID 閾值,外層查詢利用主鍵索引 id >= 隨機值 直接定位,掃描極少列數。
SELECT * FROM `news` AS t1
JOIN (
SELECT ROUND(
RAND() * (
(SELECT MAX(id) FROM `news`) - (SELECT MIN(id) FROM `news`)
) + (SELECT MIN(id) FROM `news`)
) AS id
) AS t2 ON t1.id >= t2.id
LIMIT 10;
優點:單條 SQL 完成,適合視圖、存儲過程、報表工具直接呼叫。
注意:若 ID 有大量間隙,分佈不完全均勻,但通常可接受。
| 方法 | 耗時 | 掃描列數 | 說明 |
|---|---|---|---|
| ORDER BY RAND() LIMIT 10 | ~45 秒 | 全表 (1000 萬) | filesort + temporary table |
| 方案一 (子查詢) | ~8 ms | ~10-50 | 索引範圍掃描 |
| 方案二 (JOIN) | ~12 ms | ~10-50 | 衍生表 + 索引掃描 |
WHERE id IN (隨機ID列表),應用層生成 N 個隨機 ID。TABLESAMPLE SYSTEM (10) LIMIT 10 (系統採樣) 或 BERNOULLI (伯努利採樣)。SELECT ... FROM replica.news),避免影響主庫寫入。-- 估算表總行數 (快速近似)
SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA='db' AND TABLE_NAME='news';
-- 伯努利採樣 1% (MySQL 8.0.25+)
SELECT * FROM news TABLESAMPLE BERNOULLI (1) LIMIT 1000;