在Mysql中咱們經常用order by來進行排序,使用limit來進行分頁,當須要先排序後分頁時咱們每每使用相似的寫法select * from 表名 order by 排序字段 limt M,N。可是這種寫法卻隱藏着較深的使用陷阱。在排序字段有數據重複的狀況下,會很容易出現排序結果與預期不一致的問題。html
好比如今有一張user表,表結構及數據以下:mysql
如今想根據建立時間升序查詢user表,而且分頁查詢,每頁2條,那很容易寫出sql爲:select * from user order by create_time limit pageNo,2;sql
在執行查詢過程當中會發現:
一、查詢第一頁數據時:測試
二、查詢第四頁數據時:優化
user表共有8條數據,有4頁數據,可是實際查詢過程當中第一頁與第四頁居然出現了相同的數據。code
這是什麼狀況?難道上面的分頁SQL不是先將兩個表關聯查詢出來,而後再排好序,再取對應分頁的數據嗎???htm
上面的實際執行結果已經證實現實與想像每每是有差距的,實際SQL執行時並非按照上述方式執行的。這裏實際上是Mysql會對Limit作優化,具體優化方式見官方文檔:https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html
這個是5.7版本的說明,提取幾個問題直接相關的點作下說明。排序
上面官方文檔裏面有提到若是你將Limit row_count與order by混用,mysql會找到排序的row_count行後立馬返回,而不是排序整個查詢結果再返回。若是是經過索引排序,會很是快;若是是文件排序,全部匹配查詢的行(不帶Limit的)都會被選中,被選中的大多數或者所有會被排序,直到limit要求的row_count被找到了。若是limit要求的row_count行一旦被找到,Mysql就不會排序結果集中剩餘的行了。索引
這裏咱們查看下對應SQL的執行計劃:文檔
能夠確認是用的文件排序,表確實也沒有加額外的索引。因此咱們能夠肯定這個SQL執行時是會找到limit要求的行後立馬返回查詢結果的。
不過就算它立馬返回,爲何分頁會不許呢?
官方文檔裏面作了以下說明:
若是order by的字段有多個行都有相同的值,mysql是會隨機的順序返回查詢結果的,具體依賴對應的執行計劃。也就是說若是排序的列是無序的,那麼排序的結果行的順序也是不肯定的。
基於這個咱們就基本知道爲何分頁會不許了,由於咱們排序的字段是create_time,正好又有幾個相同的值的行,在實際執行時返回結果對應的行的順序是不肯定的。對應上面的狀況,第一頁返回的name爲8的數據行,可能正好排在前面,而第四頁查詢時name爲8的數據行正好排在後面,因此第四頁又出現了。
那這種狀況應該怎麼解決呢?
官方給出瞭解決方案:
若是想在Limit存在或不存在的狀況下,都保證排序結果相同,能夠額外加一個排序條件。例如id字段是惟一的,能夠考慮在排序字段中額外加個id排序去確保順序穩定。
因此上面的狀況下能夠在SQL再添加個排序字段,好比fund_flow的id字段,這樣分頁的問題就解決了。修改後的SQL能夠像下面這樣:
SELECT * FROM user
ORDER BY create_time,id LIMIT 6,2;
再次測試問題解決!!