常見誤區:
-- 錯誤示範:全表相同隨機值
SELECT *, RAND() AS rnd FROM Orders ORDER BY rnd;
RAND() 在單一查詢中只計算一次,所有行獲得相同值,ORDER BY 失效。雖可用 RAND(CHECKSUM(NEWID())) 強制逐行計算,但效能極差且分布不均。
NEWID() 生成唯一識別碼 (GUID),每行調用產生不同值,天然適合隨機排序。
-- 全表隨機排序
SELECT * FROM Northwind.dbo.Orders ORDER BY NEWID();
-- 隨機抽樣 Top N
SELECT TOP 10 * FROM Northwind.dbo.Orders ORDER BY NEWID();
tempdb 中對 GUID 排序 (O(N log N))TOP,排序後截取| 資料量 | TOP 10 耗時 | 全表排序耗時 | tempdb 使用 |
|---|---|---|---|
| 1 萬 | 15 ms | 120 ms | 2 MB |
| 10 萬 | 45 ms | 1.8 s | 25 MB |
| 100 萬 | 210 ms | 28 s | 320 MB |
| 1000 萬 | 3.2 s | 6 分 | 4.1 GB |
-- 按頁面抽樣 1% (約 100 行/萬行)
SELECT * FROM Orders TABLESAMPLE (1 PERCENT) REPEATABLE (42);
-- 指定行數近似值
SELECT * FROM Orders TABLESAMPLE (100 ROWS);
| 優點 | 缺點 |
|---|---|
| 極快 (毫秒級),不排序,不佔 tempdb | 頁面級抽樣,同一頁全選或全不選,非真隨機行級 |
| 可重複 (REPEATABLE) | 無法配合 WHERE 過濾後抽樣 |
-- 先取得 ID 範圍
DECLARE @MinID INT, @MaxID INT, @RandomID INT;
SELECT @MinID = MIN(OrderID), @MaxID = MAX(OrderID) FROM Orders;
-- 生成隨機 ID (應用層或 T-SQL)
SET @RandomID = @MinID + CAST(RAND() * (@MaxID - @MinID) AS INT);
-- 查詢該 ID 及附近 N 筆
SELECT TOP 10 * FROM Orders
WHERE OrderID >= @RandomID
ORDER BY OrderID;
注意:ID 有間隙/刪除時分布不均;需配合 NEWID() 於小範圍二次隨機。
-- 1. 增加持久化隨機鍵欄位
ALTER TABLE Orders ADD RandomSortKey UNIQUEIDENTIFIER
DEFAULT NEWID() NOT NULL;
CREATE NONCLUSTERED INDEX IX_Orders_RandomSortKey ON Orders(RandomSortKey);
-- 2. 定期重算 (每日/每週離線作業)
UPDATE Orders SET RandomSortKey = NEWID();
-- 3. 極速抽樣
SELECT TOP 10 * FROM Orders ORDER BY RandomSortKey; -- 索引順序掃描,無排序
| 指標 | ORDER BY NEWID() | 預計算鍵 |
|---|---|---|
| 查詢耗時 (100萬行, TOP 10) | 210 ms | 0.3 ms |
| tempdb 壓力 | 高 | 零 |
| 寫入開銷 | 無 | 定期全表 UPDATE |
| 隨機品質 | 完美 | 重算週期內固定 |
-- 以 0.1% 機率選中每行
SELECT * FROM Orders
WHERE CAST(CHECKSUM(NEWID(), OrderID) & 0x7FFFFFFF AS FLOAT) / 2147483647.0 < 0.001;
優點:單次掃描、無排序、可配合 WHERE。缺點:結果行數不固定、需全表掃描。
需要精確隨機行序嗎?
├─ 否 → TABLESAMPLE (極速) / BERNOULLI (可過濾)
└─ 是 → 結果集大小?
├─ 小表 (< 5 萬行) → ORDER BY NEWID() 直觀簡單
├─ 中表 (5萬~50萬) + 偶爾查詢 → ORDER BY NEWID() 可接受
├─ 中大表 + 高頻抽樣 → 預計算 RandomSortKey 索引 (最佳)
└─ 巨表 (> 50萬) + 低頻 → 主鍵範圍 + 小範圍 NEWID() 二次隨機
CREATE PROCEDURE usp_GetRandomRecommendations
@UserID INT,
@PageSize INT = 10,
@ExcludeCategory VARCHAR(50) = NULL
AS
BEGIN
SET NOCOUNT ON;
-- 策略:用戶畫像標籤 + 隨機鍵 索引
SELECT TOP (@PageSize) p.ProductID, p.Name, p.Price, p.ImageUrl
FROM Products p
INNER JOIN UserTags ut ON p.CategoryID = ut.CategoryID
WHERE ut.UserID = @UserID
AND (@ExcludeCategory IS NULL OR p.CategoryID <> @ExcludeCategory)
AND p.IsActive = 1
ORDER BY p.RandomSortKey; -- 預計算索引,毫秒級返回
-- 若無預計算鍵,退而求其次:
-- ORDER BY CHECKSUM(NEWID(), p.ProductID) -- 仍需排序,但較 NEWID() 穩定
END;
ORDER BY NEWID() 最簡潔、標準、隨機品質最佳TABLESAMPLE 或 BERNOULLI 邏輯RAND() 做逐行隨機排序掌握這些模式,讓隨機性成為可控、可預測、高效能的系統能力,而非效能殺手。