1. 問題描述
有同時反饋,直接通過如下的sql進行分頁查詢,分頁會出現重復數據,於是乎我專門查了相關了資料,整理了一下。
-- 根據sort字段對dbname進行排序,每五百條數據一頁
SELECT * FROM (
SELECT A.*, ROWNUM RN FROM (
select * from dbname
where createtime between '20211212' and '20211213') A
WHERE ROWNUM <=7000 )
WHERE RN >6500
2. 問題分析
可能的問題原因
- 數據庫本身有重復數據:經排查,表結構有唯一索引,不存在重復數據
- 分頁數據有交集:根據這個問題:What is the default order of records for a SELECT statement in MySQL?,里邊的高贊回答可知,sql-92標准中指明如果沒有排序,那么返回數據的順序將由數據庫實現決定。
2.1 Oracle 的 order by 是穩定排序么?
根據oracle官方文檔:ORDER BY clauses,里邊有針對排序是否穩定做了說明。
EQL保證語句的結果在查詢中是穩定的。這意味着:
- 如果沒有執行更新,則即使沒有指定ORDER BY子句,或者ORDER BY句中指定的順序有聯系,同一語句也會在重復查詢時以相同的順序返回結果。
- 如果執行了更新,那么只有明確影響訂單的更改才會影響訂單;訂單不會受到其他影響。訂單可能會受到更改的影響,例如刪除或插入有助於返回頁面上或之前結果的記錄,或修改用於分組或訂購的值。
例如,在沒有ORDER BY子句的語句中,使用PAGE(0, 10)然后是PAGE(10, 10)然后是PAGE(20, 10)查詢,在沒有更新的情況下,從同一任意但穩定的結果返回連續的10條記錄。
對於帶有更新的示例,在帶有ORDER BY Num PAGE(3, 4)的語句中,初始查詢返回記錄{5、6、7、8}。然后,更新插入帶有4的記錄(在指定頁之前),刪除帶有6的記錄(在指定頁上),並插入帶有9的記錄(在指定頁之后)。更新后,同一查詢的結果將為{4、5、7、8}。這是因為:
- 插入4將所有后續結果向下移動一個。抵消3條記錄包括新記錄。
- 刪除6個班次會將所有后續結果增加一個。
- 插入9不影響此結果之前或包含的任何記錄。
從官方文檔的描述來看,只要加上order by,那么在沒有影響到該查詢條件的更新或者寫入操作,則排序是不受影響的,是穩定的。官方文檔的描述比較符合我的預期,因為我覺得要是我去實現,我就會使用穩定排序的算法去實現,而不是非穩定算法。
我看網絡上充斥着這片文章:Oracle——分頁查詢出現重復數據問題的分析與解決,該文章提到一個觀點需要唯一索引才能夠保證分頁排序不會重復。我覺得看法太淺了,相當於提出了解決方案,但是不知道為什么能夠解決沒有了解,另外就是文章感覺個人主觀猜想太強了,理論沒有依據來源的感覺,可信度就感覺比較低。根據官方文檔的說明,實際只要加上排序即可保證分頁遍歷是不會出現重復數據的。
3. 解決方法
3.1 通過排序分頁
我想這是最高效的寫法,只要在createtime 字段加上索引,則查詢和排序都會利用到該索引。
-- 根據sort字段對dbname進行排序,每五百條數據一頁
SELECT * FROM (
SELECT A.*, ROWNUM RN FROM (
select * from dbname
where createtime between '20211212' and '20211213'
order by createtime) A
WHERE ROWNUM <=7000 )
WHERE RN >6500