ORACLE中order by造成分頁不正確原因分析


       工作中遇到的問題:

                為調用方提供一個分頁接口時,調用方一直反應有部分數據取不到,且取到的數據有重復的內容,於是我按以下步驟排查了下錯誤。

1.檢查分頁頁碼生成規則是否正確。
2.檢查SQL語句是否正確。(后來確認是SQL中order by作祟,犯了想當然的錯誤,認為SQL是最不可能出問題的地方,因為分頁SQL格式與老代碼分頁SQL格式一樣,所以沒有懷疑。)
3.檢查調用方入參是否正確。
4.檢查調用方循環遍歷邊界。
5.在上述步驟驗證沒問題后,懷疑ibatis,調試到ibatis中,花費大量時間。
6.再次驗證SQL,發現問題。
經過這么多步驟,發現自己考慮問題都想復雜了,最簡單的錯誤原因往往就是其錯誤原因,那么我們就來分析為什么 order by 會造成分頁SQL出錯。

分頁SQL:
SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER) t WHERE ROWNUM<#endRow# ) WHERE rowno>=#startRow#
看似這個SQL沒有什么問題,
執行過程:
select * from table ORDER BY LIST_ORDER
1.首先取出table表的所有數據,並按照list_order排序,其中list_order可以取0,1,2,3,4,5這六個數
SELECT t.*, ROWNUM AS rowno FROM (.....) t WHERE ROWNUM<#endRow#
2.取出table表中前#endRow#個數據。
SELECT * FROM (......) WHERE rowno>=#startRow#
3.取出從第#startRow#個數據后的所有數據。
於是這樣就取出了table中#startRow#到#endRow#的所有數據,可是我們忽略了這個問題,ROWNUM是不變的嗎?答案是order by 會導致 rownum發生變化

驗證一下 比較兩個SQL 的結果。

1.SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER) t WHERE ROWNUM<6
ID CATEGORY_NAME LIST_ORDER ROWNO
23794 fdfdf 0 1
22899 上裝1 0 2
5260 薯片 0 3
5094 廚房家電 0 4
23029 涼血止血 0 5
2.SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER) t WHERE ROWNUM<11
ID CATEGORY_NAME LIST_ORDER ROWNO
23794 fdfdf 0 1
23204 子目錄222-22 0 2
23203 子目錄222-21 0 3
23202 子目錄222-20 0 4
23200 子目錄222-18 0 5
23198 子目錄222-16 0 6
22899 上裝1 0 7
5260 薯片 0 8
5094 廚房家電 0 9
23029 涼血止血 0 10

結果很明顯:

        以“涼血止血”為例,在第一個SQL中,“涼血止血”的rownum為5, 而在第二個SQL中“涼血止血”的rownum為10,他的rownum 發生了變化

       於是這樣在第三步,我們取第#startRow#個數據后的所有數據時,就會一直把最后面的“涼血止血”類似的數據給取出來,導致出現重復的錯誤,並且前面的數據會有取不到的可能性。那么為什么rownum會發生變化呢?

       對於rownum來說它是oracle系統順序分配為從查詢返回的行的編號,返回的第一行分配的是1,第二行是2,依此類推,這個偽字段可以用於限制查詢返回的總行數,且rownum不能以任何表的名稱作為前綴。
聽起來很繞口對吧,其實簡單的說就是,你去查數據庫,rownum就是oracle根據返回數據的順序給他的一個編號,誰先返回誰就是1,如果不存在order by排序條件那么它就是oracle的存儲順序。

     錯誤導致原因分析:於是當本文中取出的數據的list_order這個字段的值是一樣的時候,oracle在返回數據時,返回數據順序不是固定的,我們取前5個數據的時候,數據庫返回數據的順序,與我們取前11個數據時,數據庫返回數據的順序是完全不同的,於是他生成的rownum偽列的編號就完全不一樣,就導致了這樣的錯誤。

造成這種錯誤前提:
1.order by 排序字段不唯一
2.分頁使用的是類似以下SQL的結構
SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM ( select * from table ORDER BY LIST_ORDER) t WHERE ROWNUM<#endRow# ) WHERE rowno>=#startRow#
3.數據庫的數據足夠多,這樣才比較容易發生rownum生成不一致

解決辦法:
1.提取rownum到外部:

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER) t ) WHERE rowno>=#startRow# AND ROWNUM<#endRow#

優點:適用各種order by不同字段,因為內部取值SQL是不變的,所以取值順序是不變的,分頁肯定不會出錯
缺點:SQL效率變低,每次都相當於取出了所有的數據,然后再進行遍歷比較,依賴於oracle的存儲順序,當oracle存儲順序發生變化時,需要注意。(當然那時候很多類型的SQL都要注意了

2.order by后面加上唯一性字段(類似主鍵id) :

SELECT * FROM (SELECT t.*, ROWNUM AS rowno FROM (select * from table ORDER BY LIST_ORDER,id) t ) WHERE rowno>=#startRow# AND ROWNUM<#endRow#

優點:修改簡單,原來的代碼不用做過多更改
缺點:sql效率有可能會比第一種修改方式更加低,因為在根據list_order排序后,還要根據id再排一次序,當數據量比較多時,SQL可能會很慢。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM