在 Oracle 開發中,ROWNUM 與 ROWID 是兩個最常被誤解的「偽欄位」。它們雖同樣不儲存於資料塊中,但運作機制、使用場景截然不同。錯誤使用 ROWNUM 進行分頁查詢,是導致效能災難與邏輯錯誤的常見主因。本文將從底層機制剖析兩者差異,並提供最佳實踐模式。
| 條件 | 結果 | 原因 |
|---|---|---|
WHERE ROWNUM = 1 | ✅ 正確返回第一行 | 首行符合條件 |
WHERE ROWNUM < N (N>1) | ✅ 返回前 N-1 行 | 逐行遞增檢查皆通過 |
WHERE ROWNUM = N (N>1) | ❌ 空集 | 第一行即被過濾,後續無機會產生 N |
WHERE ROWNUM > N | ❌ 永遠空集 | 同理,首行即不符合 |
WHERE ROWNUM > 0 | ✅ 全表掃描 | 所有行皆符合 |
查詢 SELECT * FROM t WHERE zz > 20 AND ROWNUM < 10 時,Oracle 採用 逐行過濾+即時停止 機制:
zz > 20 的行< 10此特性使 ROWNUM 成為「Top-N 查詢」的高效利器,但也導致無法直接實現「第 11-20 行」這類跳頁需求。
WHERE ROWID = '...' 為單行存取最快方式雖理論最快,但 ROWID 難以人工記憶、無業務語意、跨會話不保證穩定,不建議作為應用層主鍵或持久化引用。適合場景:內部工具快速定位、PL/SQL 批次處理中暫存行地址、刪除重複資料(保留 MIN(ROWID))。
SELECT * FROM (
SELECT t.*, ROWNUM rn FROM (
SELECT * FROM orders WHERE status = 'OPEN' ORDER BY created_dt DESC
) t WHERE ROWNUM <= :end_row
) WHERE rn >= :start_row;
ORDER BY 確保業務排序ROWNUM <= :end_row 觸發早期停止rn >= :start_row 完成分頁SELECT * FROM orders
WHERE status = 'OPEN'
ORDER BY created_dt DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
MINUS 集合運算雖可實現分頁,但需掃描兩次完整結果集並排序比對,資源消耗遠高於子查詢模式,生產環境嚴禁使用。
| 維度 | ROWNUM | ROWID |
|---|---|---|
| 本質 | 邏輯序號(虛擬) | 物理地址(真實) |
| 穩定性 | 查詢每次重新計算 | 行不移動則固定 |
| 典型用途 | Top-N、分頁、抽樣 | 內部定位、去重、批次更新 |
| 索引關聯 | 無 | 隱含於所有 B-tree 索引葉節點 |
掌握兩者本質差異,才能在 SQL 調優與架構設計中做出正確選擇。記住:ROWNUM 是「第幾個被返回」,ROWID 是「存在哪裡」。