經典分頁寫法:
SELECT * FROM (
SELECT t.*, ROWNUM rn FROM (
SELECT ... ORDER BY orderseq, biz_delete_time NULLS LAST
) t
WHERE ROWNUM < :end
) WHERE rn >= :start;
實測發現:第 1-3 頁正常,第 4 頁起資料重複。經排查,ORDER BY 欄位組合不唯一 導致 ROWNUM 賦值不穩定。
Oracle Ask Tom 明確指出:
「分頁查詢的
ORDER BY必須排序唯一鍵。若非唯一,必須在末尾補上唯一欄位(如 PK),否則 ROWNUM 分配順序不確定,導致分頁錯亂。」
-- 原 ORDER BY orderseq, biz_delete_time NULLS LAST
-- 修正:補上主鍵 eid 保證唯一
ORDER BY orderseq, biz_delete_time NULLS LAST, eid
修正後重跑分頁,第 4 頁及以後資料不再重複,翻頁順暢。
ROW_NUMBER() OVER (ORDER BY ...) (10g+) 替代 ROWNUM,語意更清晰且支援 PARTITION BY。OFFSET ... ROWS FETCH NEXT ... ROWS ONLY,由優化器保證正確性。ROWNUM 在 WHERE ROWNUM < N 過濾時即分配,若排序不唯一,相同排序鍵的行在不同執行中物理順序可能不同(受平行查詢、統計資訊、資料分布影響),導致被截取的行集合不同。
一個 PK 欄位,解決分頁重複/遺漏的所有疑難雜症。