分頁查詢是應用開發高頻需求,Oracle、DB2、MySQL 在語法與優化機制上存在顯著差異。本文系統對比三種核心方案(分析函數/ROWNUM/LIMIT),提供跨平台兼容寫法與執行計劃優化技巧。
| 方案 | Oracle | DB2 | MySQL | 標準 SQL |
|---|---|---|---|---|
| 分析函數 | `ROW_NUMBER() OVER` ✓ | `ROW_NUMBER() OVER` ✓ | 8.0+ ✓ | SQL:2003 ✓ |
| 偽列 | `ROWNUM` ✓ | - | - | - |
| LIMIT/OFFSET | 12c- ✗ / 12c+ ✓ | - | `LIMIT n OFFSET m` ✓ | SQL:2008 ✓ |
| FETCH FIRST | 12c+ ✓ | ✓ | 8.0+ ✓ | SQL:2008 ✓ |
測試表:`TAB (TNAME, TABTYPE)`,100萬行,`TNAME` 有索引
SELECT tname, tabtype FROM (
SELECT tname, tabtype, ROW_NUMBER() OVER (ORDER BY tname) rn FROM tab
) WHERE rn BETWEEN 100 AND 150;
優勢:
SELECT tname, tabtype FROM (
SELECT tname, tabtype, ROWNUM rn FROM tab WHERE ROWNUM <= 150
) WHERE rn >= 100;
關鍵限制:
SELECT * FROM (
SELECT tname, tabtype, ROWNUM rn FROM (
SELECT tname, tabtype FROM tab ORDER BY tname
) WHERE ROWNUM <= 150
) WHERE rn >= 100;
性能陷阱:大偏移量時仍需掃描前 N 行,`OFFSET 1000000` 極慢。
SELECT tname, tabtype FROM tab ORDER BY tname
OFFSET 99 ROWS FETCH NEXT 50 ROWS ONLY;
內部轉換為 `ROW_NUMBER()`,性能等效,語法最標準。
測試數據:`MYNUMBER (ID, NAME)`,ID 有重複值
-- 方案 A:DISTINCT + ROWNUM
SELECT id, name FROM mynumber WHERE id IN (
SELECT id FROM (SELECT DISTINCT id FROM mynumber) WHERE ROWNUM <= 10
);
-- 方案 B:DENSE_RANK() — 推薦
SELECT * FROM (
SELECT id, name, DENSE_RANK() OVER (ORDER BY id) dr FROM mynumber
) WHERE dr <= 10;
-- RANK() 允許跳號,DENSE_RANK() 不跳號
SELECT * FROM (
SELECT id, name, RANK() OVER (ORDER BY id) rk FROM mynumber
) WHERE rk <= 10;
-- 簡單 ROWNUM
SELECT id, name FROM mynumber WHERE ROWNUM <= 10;
-- ROW_NUMBER() — 確定性排序
SELECT * FROM (
SELECT id, name, ROW_NUMBER() OVER (ORDER BY id) rn FROM mynumber
) WHERE rn <= 10;
| 操作 | 含義 | 優化提示 |
|---|---|---|
| WINDOW SORT PUSHED RANK | 分析函數下推 predicate | 最佳,索引排序可避免排序 |
| COUNT STOPKEY | ROWNUM 早期停止 | 小偏移量高效 |
| WINDOW SORT | 全量排序後再過濾 | 大偏移量災難,需改寫 |