ORACLE中order by形成分頁不正確緣由分析

       工做中遇到的問題:sql

                爲調用方提供一個分頁接口時,調用方一直反應有部分數據取不到,且取到的數據有重複的內容,因而我按如下步驟排查了下錯誤。數據庫

1.檢查分頁頁碼生成規則是否正確。
2.檢查SQL語句是否正確。(後來確認是SQL中order by做祟,犯了想固然的錯誤,認爲SQL是最不可能出問題的地方,由於分頁SQL格式與老代碼分頁SQL格式同樣,因此沒有懷疑。)
3.檢查調用方入參是否正確。
4.檢查調用方循環遍歷邊界。
5.在上述步驟驗證沒問題後,懷疑ibatis,調試到ibatis中,花費大量時間。
6.再次驗證SQL,發現問題。
通過這麼多步驟,發現本身考慮問題都想複雜了,最簡單的錯誤緣由每每就是其錯誤緣由,那麼咱們就來分析爲何 order by 會形成分頁SQL出錯。oracle

分頁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 的結果。spa

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 發生了變化code

       因而這樣在第三步,咱們取第#startRow#個數據後的全部數據時,就會一直把最後面的「涼血止血」相似的數據給取出來,致使出現重複的錯誤,而且前面的數據會有取不到的可能性。那麼爲何rownum會發生變化呢?blog

       對於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生成不一致ci

解決辦法:
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可能會很慢。

相關文章
相關標籤/搜索