推薦系統、廣告輪播、測試資料抽樣常需「隨機取 N 筆」。單表小資料量可全表掃描 ORDER BY RAND(),但大表或多表 Join 時效能急劇下降。本文提供分層優化思路。
# PHP 範例
$list = $db->query("SELECT id FROM `table`"); // 只查主鍵
$keys = array_column($list, 'id');
$randKey = $keys[mt_rand(0, count($keys)-1)]; // mt_rand 比 rand 快 4 倍
$row = $db->query("SELECT * FROM `table` WHERE id = ?", $randKey);
優化點:
利用 RAND() * MAX(id) 產生隨機游標,配合索引快速定位:
SELECT * FROM `table` WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM `table`))) ORDER BY id LIMIT 1;
原理: MAX(id) 取得最大主鍵,乘以 RAND() 得到隨機位移,WHERE id >= ... 利用主鍵索引直接定位到該位置附近的第一筆資料。
注意: 若 ID 不連續(有刪除),分布不均勻;可改用 OFFSET 但需配合覆蓋索引。
SELECT m.*, s.* FROM ( SELECT id FROM main_table WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM main_table))) ORDER BY id LIMIT 10 ) m LEFT JOIN sub_table s ON m.id = s.main_id;
MySQL 5.6+ 優化器會將衍生表物質化,避免全表掃描。
| 連接類型 | 語法 | 特性 |
|---|---|---|
| 交叉連接 | CROSS JOIN / JOIN / , | 笛卡爾積,無條件慎用 |
| 內連接 | INNER JOIN | 僅返回匹配行,等值連接 |
| 左外連接 | LEFT [OUTER] JOIN | 保留左表所有行,右表無匹配填 NULL |
| 右外連接 | RIGHT [OUTER] JOIN | 保留右表所有行 |
SELECT u.id, u.name FROM user u LEFT JOIN user_action a ON u.id = a.user_id WHERE a.user_id IS NULL; -- a.user_id 必須定義 NOT NULL
USE INDEX(idx_name) 或 IGNORE INDEX(idx_name) 引導優化器。STRAIGHT_JOIN 強制按 FROM 子句順序讀表,減少優化器枚舉成本。SELECT M.*, SS.SensorCode, SS.SensorStatus, TP.PricingStrategyId
FROM (
SELECT T.*, BS.ParkStatus, CA.CantonName, SE.SectionName
FROM (
SELECT A.*, B.BerthId, B.BerthCode, B.BerthStatus
FROM (SELECT AreaId, AreaCode, AreaName FROM SYS_Area WHERE AreaCode='110') A
LEFT JOIN SYS_Berth B ON B.AreaId = A.AreaId
) T
JOIN SYS_BerthStatus BS ON T.BerthCode = BS.BerthCode
JOIN SYS_Canton CA ON T.CantonId = CA.CantonId
JOIN SYS_Section SE ON T.SectionId = SE.SectionId
) M
LEFT JOIN SYS_Sensor SS ON M.BerthCode = SS.BerthCode
LEFT JOIN TRA_PricingBerth TP ON TP.BerthCode = M.BerthCode
ORDER BY M.BerthCode ASC
LIMIT 20 OFFSET 0;
STRAIGHT_JOIN 固定順序,避免優化器錯誤選擇驅動表。隨機查詢無銀彈,需根據資料量、即時性、一致性要求選型:小表用應用層抽樣+快取,大表用索引友善的 SQL 寫法,多表 Join 時先縮小驅動表再關聯。持續觀察 EXPLAIN 輸出,配合索引與優化器提示,才能在增長的資料量下保持查詢效能。