分頁查詢是應用系統核心需求。Oracle 無原生 LIMIT/OFFSET(12c 前),需透過 ROWNUM 機制實作。本文解析兩種主流寫法的效能差異與優化原理。
SELECT *
FROM (SELECT a.*, ROWNUM rn
FROM (SELECT * FROM table_name) a
WHERE ROWNUM <= 40)
WHERE rn >= 21;
適用場景:第 2 頁,每頁 20 筆(第 21-40 筆)。
SELECT *
FROM (SELECT a.*, ROWNUM rn
FROM (SELECT * FROM table_name) a)
WHERE rn BETWEEN 21 AND 40;
WHERE ROWNUM <= 40 可被 CBO 推入最內層查詢。一旦結果集超過 40 筆,內層查詢立即停止(COUNT STOPKEY 操作),避免全表掃描。BETWEEN 21 AND 40 位於最外層,無法下推至內層(內層不知 rn 定義)。最內層需產生所有符合條列資料,再由外層過濾,I/O 與記憶體壓力大幅增加。-- 寫法一典型計畫
| Id | Operation | Name | Rows | Bytes | Cost |
|----|--------------------------|------|------|-------|------|
| 0 | SELECT STATEMENT | | 20 | 2000 | 10 |
|* 1 | VIEW | | 20 | 2000 | 10 |
|* 2 | COUNT STOPKEY | | | | |
| 3 | VIEW | | 40 | 4000 | 10 |
| 4 | TABLE ACCESS FULL | TBL | 1000 | 10000 | 10 |
-- 寫法二典型計畫
| Id | Operation | Name | Rows | Bytes | Cost |
|----|---------------------|------|------|-------|------|
| 0 | SELECT STATEMENT | | 20 | 2000 | 100 |
|* 1 | VIEW | | 20 | 2000 | 100 |
|* 2 | FILTER | | | | |
| 3 | VIEW | | 1000 | 10000 | 100 |
| 4 | TABLE ACCESS FULL| TBL | 1000 | 10000 | 100 |
-- 記住上一頁最後一筆的排序鍵值
SELECT * FROM table_name
WHERE sort_key > :last_sort_key
ORDER BY sort_key
FETCH FIRST 20 ROWS ONLY; -- 12c+ 語法
SELECT *
FROM (SELECT t.*, ROW_NUMBER() OVER (ORDER BY sort_col) rn
FROM table_name t)
WHERE rn BETWEEN 21 AND 40;
ROW_NUMBER()OFFSET ... ROWS FETCH NEXT ... ROWS ONLY 標準語法ROWNUM 直接配合 ORDER BY(順序錯誤,先取 ROWNUM 再排序)