在處理百萬筆資料量的 Oracle 資料庫時,分頁查詢的效能往往是開發者最頭疼的問題。本文將分享實際測試過程,對比 JDBC 原生 Absolute 定位、Oracle ROWNUM 偽欄位、以及 ResultSet Next() 迴圈三種分頁方式的優劣,並探討實務上是否值得為了效能犧牲 SQL 可移植性。
最直觀的作法是使用 ResultSet.absolute(int row) 直接跳轉到指定行。
OutOfMemoryError結論:Oracle JDBC 實作 Absolute 時會逐行讀取並緩存前面所有資料,記憶體消耗隨定位位置線性增長,完全不適合深分頁。設定 FetchSize 僅能帶來 10% 左右的提升,治標不治本。
採用經典三層嵌套寫法:
SELECT * FROM (
SELECT A.*, ROWNUM RN FROM (
SELECT * FROM demo_table
) A WHERE ROWNUM <= 200010
) WHERE RN >= 200000
特點:隨著分頁深入,效能明顯下降,因為資料庫仍需掃描並捨棄前面所有滿足條件的行。但至少不會 OOM。
檢視 iBatis 原始碼發現,其分頁實作對 TYPE_FORWARD_ONLY 的 ResultSet 採用 next() 迴圈定位,而非 absolute()。實測證明:
原理:Oracle JDBC 驅動對 Forward-Only 游標的 next() 做了優化,不會將跳過的行完整物件化載入記憶體,僅在網路層面丟棄封包。雖比 ROWNUM 慢一倍,但屬同一數量級,且完全避免 OOM 風險。
綜合比較:
| 方案 | 20萬 | 90萬 | 記憶體 | 跨DB |
|---|---|---|---|---|
| Absolute | 5s | OOM | 高 | 是 |
| ROWNUM | 2-3s | 13s | 低 | 否 |
| Next()迴圈 | ~5s | 22s | 極低 | 是 |
決策建議:
OFFSET ... FETCH,但需接受 SQL 鎖定 Oracle。測試環境較舊(Oracle 9i),新版本驅動與資料庫對游標優化更佳。核心啟示在於:理解驅動實作機制(Absolute vs Next)比盲目追求「最快 SQL」更重要。選擇適合業務場景、可維護的方案,才是架構師的本分。