工做中遇到的問題: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可能會很慢。