分頁是應用開發高頻需求。本文梳理 Oracle 歷版本分頁寫法演進,重點講解經典 ROWNUM 三層嵌套原理、ORDER BY 陷阱、12c 引入的標準 OFFSET ... FETCH 語法,並提供性能優化建議。
場景:每頁 3 條,查第 2 頁(即第 4-6 條),按 columnName 降序。
SELECT s.*
FROM (
SELECT rownum AS rn, c.*
FROM (
SELECT * FROM tableName ORDER BY columnName DESC
) c
WHERE rownum <= 3 * 2 -- 上限:pageSize * pageNumber
) s
WHERE s.rn >= 3 * (2 - 1) + 1; -- 下限:pageSize * (pageNumber - 1) + 1
ORDER BY 先排序,生成有序結果集。ROWNUM(從 1 遞增),並截取「上限」行(WHERE rownum <= 上限),利用 ROWNUM 特性提前終止掃描。SELECT *
FROM (
SELECT rownum AS rn, c.*
FROM (
SELECT * FROM tableName ORDER BY columnName ASC
) c
WHERE rownum < 2 * 3 + 1
) s
WHERE s.rn > 3 * (2 - 1);
| 陷阱 | 後果 | 正確做法 |
|---|---|---|
| 外層先 WHERE ROWNUM 再 ORDER BY | 結果順序錯亂 | 最內層 ORDER BY,中間層賦 ROWNUM |
| 直接 WHERE ROWNUM BETWEEN ... | ROWNUM 從 1 開始,BETWEEN 4 AND 6 永無結果 | 三層嵌套或 12c FETCH FIRST |
| 大偏移量(如第 10000 頁) | 全表掃描+大量排序+棄取,極慢 | 使用鍵值分頁、游標分頁、或物化視圖預計算 |
| 無索引支持 ORDER BY 列 | 全表排序,內存/臨時表空間壓力大 | 為分頁欄位建立索引,或覆蓋索引包含 SELECT 列 |
SELECT * FROM tableName
ORDER BY columnName DESC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
OFFSET n ROWS:跳過前 n 行(對應 (pageNumber-1)*pageSize)。FETCH NEXT m ROWS ONLY:取後續 m 行(對應 pageSize)。WHERE id > last_id ORDER BY id FETCH FIRST n ROWS ONLY,避免 OFFSET 掃描。CREATE INDEX idx_table_col ON tableName(columnName) INCLUDE (col1, col2...)(12c+),實現純索引掃描無需回表。RESULT_CACHE Hint 或服務端緩存。setFetchSize 流式獲取,適合批量導出場景。
掌握分頁查詢的演進與優化精髓,能讓您的應用在海量數據下依然保持毫秒級響應。