如何優雅地實現分頁查詢

分頁功能是很常見的功能,特別是當數據量愈來愈大的時候,分頁查詢是必不可少的。實現分頁功能有不少種方式,若是使用的ORM框架是mybatis的話,有開源的分頁插件可使用,如:Mybatis-PageHelper。若是不使用分頁插件,那麼就須要手動分頁了,因爲不一樣的數據庫實現分頁的SQL語句並不一致,如Mysql使用的是limit關鍵字,而Oracle使用的是rownum,因此本文本文講解的分頁方案只適用於Mysql數據庫。mysql

基於limit的分頁方案sql

首先講講分頁操做必須知足的幾個要求:一個是有序性,一個是不重複。有序性能夠當作是不重複的前提條件,由於假如數據是無序的,那麼就不能保證多個分頁之間是不重複的。所以分頁操做每每須要先對數據進行排序,而後再加上分頁條件。咱們講的第一種方案是基於limit的分頁方案,也是不少分頁插件使用的分頁方案。咱們先來看看咱們的測試數據。數據庫

先看一下表結構:bash

mysql> desc user;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| id    | bigint(20) | NO   | PRI | NULL    |       |
| name  | char(50)   | NO   |     | NULL    |       |
+-------+------------+------+-----+---------+-------+
2 rows in set
複製代碼

能夠看到咱們的user表只有2列,分別是bigint型的id和char型的name。mybatis

接下來看下錶數據:架構

mysql> select count(*) from user;
+----------+
| count(*) |
+----------+
|    45116 |
+----------+
1 row in set

mysql> select * from user order by id asc limit 10;
+----+--------+
| id | name   |
+----+--------+
|  0 | user_0 |
|  1 | user_1 |
|  2 | user_2 |
|  3 | user_3 |
|  4 | user_4 |
|  5 | user_5 |
|  6 | user_6 |
|  7 | user_7 |
|  8 | user_8 |
|  9 | user_9 |
+----+--------+
10 rows in set
複製代碼

能夠看到數據總行數大概45000條。框架

基於limit實現分頁是比較簡單的:測試

mysql> select * from user order by id asc limit 10000,10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10000 | user_10000 |
| 10001 | user_10001 |
| 10002 | user_10002 |
| 10003 | user_10003 |
| 10004 | user_10004 |
| 10005 | user_10005 |
| 10006 | user_10006 |
| 10007 | user_10007 |
| 10008 | user_10008 |
| 10009 | user_10009 |
+-------+------------+
10 rows in set
複製代碼

其中,limit後面的第一個參數表示下標,也就是從第10000行記錄開始取,第二個參數表示總共取10行記錄。ui

使用limit實現分頁功能使用起來很是簡單,可是有沒有什麼問題呢?spa

咱們先來回顧一下前面說的分頁須要知足的2個要素:有序性和不重複。上述的語句咱們已經使用了order by 進行排序,因此是能夠知足有序性的,但知足了不重複了嗎?假設在查詢當前頁跟下一頁之間插入了一條記錄,且該數據的id小於當前頁記錄中最大的id,會怎麼樣呢?咱們測試一下就知道了:

mysql> select * from user order by id asc limit 10000,10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10000 | user_10000 |
| 10001 | user_10001 |
| 10002 | user_10002 |
| 10003 | user_10003 |
| 10004 | user_10004 |
| 10005 | user_10005 |
| 10006 | user_10006 |
| 10007 | user_10007 |
| 10008 | user_10008 |
| 10009 | user_10009 |
+-------+------------+
10 rows in set

mysql> insert into user(id,name) values(-1,'user_-1');
Query OK, 1 row affected
mysql> select * from user order by id asc limit 10010,10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10009 | user_10009 |
| 10010 | user_10010 |
| 10011 | user_10011 |
| 10012 | user_10012 |
| 10013 | user_10013 |
| 10014 | user_10014 |
| 10015 | user_10015 |
| 10016 | user_10016 |
| 10017 | user_10017 |
| 10018 | user_10018 |
+-------+------------+
10 rows in set
複製代碼

能夠看到,當咱們在相鄰的2頁查詢之間插入一條記錄的時候,後面一頁跟前面一頁有記錄重複了(id爲10009的記錄在相鄰2頁中都出現了)。緣由在於插入一條記錄以後,分頁結構已經改變了,因此纔會出現重複數據。

所以,使用limit進行分頁彷佛不是很優雅啊,接下來將介紹另一種分頁的寫法。

基於limit與比較的分頁方案

另一種分頁的寫法能夠這樣考慮,好比咱們要取的是從第10000行開始的10行記錄,那麼咱們能夠先把大於或等於10000行的數據查出來並排序,而後再取出前10行記錄,這樣也能夠完成分頁。接下來看具體的SQL語句:

mysql> select * from user where id >=10000 order by id asc limit 10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10000 | user_10000 |
| 10001 | user_10001 |
| 10002 | user_10002 |
| 10003 | user_10003 |
| 10004 | user_10004 |
| 10005 | user_10005 |
| 10006 | user_10006 |
| 10007 | user_10007 |
| 10008 | user_10008 |
| 10009 | user_10009 |
+-------+------------+
10 rows in set
複製代碼

那麼這種寫法能夠防止上面出現的問題嗎?咱們作個試驗就知道了。

mysql> select * from user where id >=10000 order by id asc limit 10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10000 | user_10000 |
| 10001 | user_10001 |
| 10002 | user_10002 |
| 10003 | user_10003 |
| 10004 | user_10004 |
| 10005 | user_10005 |
| 10006 | user_10006 |
| 10007 | user_10007 |
| 10008 | user_10008 |
| 10009 | user_10009 |
+-------+------------+
10 rows in set

mysql> insert into user(id,name) values(-4,'user_-4');
Query OK, 1 row affected
mysql> select * from user where id >=10010
 order by id asc limit 10;
+-------+------------+
| id    | name       |
+-------+------------+
| 10010 | user_10010 |
| 10011 | user_10011 |
| 10012 | user_10012 |
| 10013 | user_10013 |
| 10014 | user_10014 |
| 10015 | user_10015 |
| 10016 | user_10016 |
| 10017 | user_10017 |
| 10018 | user_10018 |
| 10019 | user_10019 |
+-------+------------+
10 rows in set
複製代碼

能夠看到,當在相鄰的兩頁查詢之間插入數據時,分頁查詢結果不會出現重複。其實也很好理解,由於雖然插入記錄後,分頁的結構變了,可是因爲咱們如今的分頁查詢是從固定的id開始查的,因此插入新的數據對後面的分頁結果沒有影響。

固然,這種分頁查詢也是有限制的。其只適用於用來排序的列具備惟一性的狀況,在上述例子中,id列是主鍵,因此具備惟一性,故可使用這種方式分頁。若是用來排序的列不具備惟一性,好比說是時間戳,那麼這種分頁方式也可能出現重複,你們能夠想一想是爲何。

若是以爲這篇文章對你有幫助,能夠掃描下方二維碼,關注個人公衆號「Java架構沉思錄」。

相關文章
相關標籤/搜索