MySQL分頁在表比較大的時候,分頁就會出現性能問題,MySQL的分頁邏輯以下:好比select * from user limit 100000,10前端
它是先執行select * from user 掃描知足這個SQL語句,拿到執行結果後, 一頁一頁的找到行號爲100000的行,返回接下來的10行數據,出現性能問題的緣由有兩個,1:它先全表掃描了,整個表,而不是掃描到了知足條件的數據就不掃描了,好比select * from user limit 1,10 這個,它不是掃描到知足條件的10行數據就完事了,而是掃描了整個表,而後從這個結果集中從上往下掃描,只到找到行號爲1的後面10行數據,這裏出現性能問題的緣由2就在於MySQL的尋找行號的邏輯是怎麼尋找的,是否是像若是是像數組那樣經過下標一步定位行號就不存在頁碼大小的問題了,可是MySQL不是一步到位的找到這個頁碼的,具體是怎麼找到頁碼的感興趣的能夠去看MySQL的源碼,咱們能作的就是將MySQL的邏輯轉換爲直接定位數據的位置。java
好比Mybatis 上的SQL語句爲 git
<select id="queryUserListLikeName" parameterType="java.lang.String" resultType="com.entity.user">github
select
<include refid="Base_Column_List" />
from user t
WHERE t.name LIKE '%${name}%'
order by id desc
</select>spring
mybatis的 PageHelper 插件會在上面直接加上 limit 語句,源碼以下sql
public class MySqlDialect extends AbstractHelperDialect {
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
if (page.getStartRow() == 0) {
sqlBuilder.append(sql);
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getPageSize());
} else{
sqlBuilder.append(sql);
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getStartRow());
sqlBuilder.append(",");
sqlBuilder.append(page.getPageSize());
pageKey.update(page.getStartRow());
}
pageKey.update(page.getPageSize());
return sqlBuilder.toString();
}apache
就是直接調用MySQL的分頁limit函數。json
如何mybatis的PageHelper插件能將咱們的SQL語句改爲以下,那就大大提升大表的翻頁查詢效率,我本人親七萬行數據的表分頁到最後一頁這種方式比直接limit的方式快10倍,更大的表效率更大,其實原理很簡單,咱們給查詢結果集加一個行號,查詢出ID,和行號,再和原表經過ID關聯,由於關聯走了索引,索引速度很快,而後直接經過行號定位數據,速度大大提升數組
select id, name from springboot
(Select id as id2,(@rowNum:=@rowNum+1) as rowNo From user,(Select (@rowNum :=0) ) b) r ,
user t
where r.id2= t.id and r.rowNo> 100000 and t.name like '%小明%' order by id desc LIMIT 10
咱們來修改mybatis的源碼:其實很是簡單。以下
不少人可能mybaits的分頁插件都沒用過,我這裏也將其所有使用過程。
我用的springboot
在pom.xml中引入:
<!-- 分頁插件pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 分頁插件pagehelper -->
若是你的mybatis版本和它的不一樣可能會提示有的jar沒有啥的,個人用的是
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
接下來就直接使用就好了好比在controller中直接使用
@RequestMapping(value = "/")
@ResponseBody
public Object say(HttpServletRequest request) {
PageHelper.startPage(2, 4);//僅僅一行代碼就搞定,沒有其餘的地方要改了。其餘代碼原來怎麼寫仍是怎麼寫
List<user> list = userMapper.queryUserListLikeName("小明");
pagedata pagedata= new pagedata(list);//這個pagedata對象是我本身寫的,我嫌mybatis提供的太囉嗦,主要是 從list中拿到實際的對象。
return pagedata;
}
接下來咱們來修改mybatis分頁插件的拼接limit語句的邏輯代碼,方法很是簡單,新建一個這樣的類,下面的的代碼所有不要改,包名,類名都不能改。其目的就是利用Java類加載機制,替代其原來jar包裏面有的這個對象,由於這個對象已經存在了,Java就不會再去加載其原來插件裏面的這個對象了,從而巧妙的修改了其源碼。
package com.github.pagehelper.dialect.helper;
import org.apache.ibatis.cache.CacheKey;
import com.github.pagehelper.Page;
import com.github.pagehelper.dialect.AbstractHelperDialect;
/**
* @author yangjiangcai
* qq 1097657841
*/
public class MySqlDialect extends AbstractHelperDialect {
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sql= sql.toLowerCase();//所有轉換成小寫形式
if (page.getStartRow() == 0) {
sqlBuilder.append(sql);
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getPageSize());
}
else if(page.getStartRow()>10000&&this.inSingletonTable(sql)){//判斷是不是大頁碼而且單表查詢
String[] tables = this.getTableName(sql);
String sql1 =sql.split(tables[0])[0];
sqlBuilder.append(sql1);
sqlBuilder.append(" (Select id as id2,(@rowNum:=@rowNum+1) as rowNo From ");
sqlBuilder.append(tables[0]);
sqlBuilder.append(",(Select (@rowNum :=0) ) b) r ,");
sqlBuilder.append(tables[0]);
sqlBuilder.append(" ");
sqlBuilder.append(tables[1]!=null?tables[1]:" ");
sqlBuilder.append(" where r.id2= ");
sqlBuilder.append(tables[1]!=null?tables[1]:tables[0]);
sqlBuilder.append(".id ");
sqlBuilder.append(" and r.rowNo> ");
sqlBuilder.append(page.getStartRow());
if (sql.contains("where")) {//拼接原來SQL語句中的where語句後面的語句
sqlBuilder.append(" and ");
sqlBuilder.append(sql.split("where")[1]);
}else {
//拼接原有的SQL表名後面的一段後面
if (tables[1]!=null) {//表有別名
String[] sql2 =sql.split(tables[1]);
sqlBuilder.append(" ");
sqlBuilder.append(sql2.length>1?sql2[1]:" ");
}else {
String[] sql2 =sql.split(tables[0]);
sqlBuilder.append(" ");
sqlBuilder.append(sql2.length>1?sql2[1]:" ");
}
}
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getPageSize());
}else{
sqlBuilder.append(sql);
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(page.getStartRow());
sqlBuilder.append(",");
sqlBuilder.append(page.getPageSize());
pageKey.update(page.getStartRow());
}
pageKey.update(page.getPageSize());
return sqlBuilder.toString();
}
private boolean inSingletonTable(String sql) {
if (sql.contains("join")||sql.contains("JOIN")) {
return false;
}
if (sql.contains("where")) {
if (sql.contains("from")) {
String tables= sql.split("from")[1].split("where")[0];
if (tables.contains(",")) {
return false;
}
}
}
return true;
}
private String[] getTableName(String sql) {
String[] tables = new String[2];
if (sql.contains("where")) {
String tablenames = sql.split("from")[1].split("where")[0];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
} else if (sql.contains("group")&&!sql.contains("order")) {
String tablenames = sql.split("from")[1].split("group")[0];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
} else if (sql.contains("order")&&!sql.contains("group")) {
String tablenames = sql.split("from")[1].split("order")[0];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
} else if (sql.contains("order")&&sql.contains("group")) {
int orderIndex =sql.indexOf("order");
int groupIndex =sql.indexOf("group");
if (orderIndex<groupIndex) {
String tablenames = sql.split("from")[1].split("order")[0];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
}else {
String tablenames = sql.split("from")[1].split("group")[0];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
}
}else if (!sql.contains("where")&&!sql.contains("order")&&!sql.contains("group")) {
String tablenames = sql.split("from")[1];
tablenames = this.removekg(tablenames);//刪除表名先後的空格
if (tablenames.contains(" ")) {
tables=tablenames.split(" ");
return tables;
}else {
tables[0]=tablenames;
return tables;
}
}
return tables;
}
//刪除字符串兩頭的空格
private String removekg(String textContent) {
textContent = textContent.trim();
while (textContent.startsWith(" ")) {//這裏判斷是否是全角空格
textContent = textContent.substring(1, textContent.length()).trim();
}
while (textContent.endsWith(" ")) {
textContent = textContent.substring(0, textContent.length() - 1).trim();
}
return textContent;
}
}
完了,邏輯就這樣簡單。這裏我是給他加了一個分支邏輯,當頁碼很大的時候,而且是單表查詢的時候執行我這個分頁SQL的拼接邏輯,多表關聯的之後我想到好方法了再帖出來。
前端收到的{"data":[{"id":1,"name":"小明55"}],"pageNum":1,"pageSize":4,"total":1,"pages":1}
List<user> list = userMapper.queryUserListLikeName("小明");
//簡單封裝
pagedata pagedata= new pagedata(list);
return pagedata;
pagedata 對象的代碼我也帖到下面來,大家徹底能夠不使用,想用的就用吧,這個對象是純Java寫的,不依賴任何依賴
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class pagedata implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
// Page{count=true, pageNum=1, pageSize=2, startRow=0, endRow=2, total=10, pages=5, reasonable=false, pageSizeZero=false}
private List data;
private int pageNum;
private int pageSize;
private int total;
private int pages;
public pagedata(List list) {
if (list instanceof com.github.pagehelper.Page) {
this.data=new ArrayList<>();
for (Object object : list) {
data.add(object);
}
String strs= list.toString();
this.pageNum=Integer.parseInt(getVluse(strs,"pageNum"));
this.pageSize= Integer.parseInt(getVluse(strs,"pageSize"));
this.total= Integer.parseInt(getVluse(strs,"total")) ;
this.pages= Integer.parseInt(getVluse(strs,"pages"));
}
}
public List getData() {
return data;
}
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public int getTotal() {
return total;
}
public int getPages() {
return pages;
}
@Override
public String toString() {
return "pagedata [data=" + data + ", pageNum=" + pageNum + ", pageSize=" + pageSize
+ ", total=" + total + ", pages=" + pages + "]";
}
/* * 直接從json字符串中獲取對應key的value值 * */ public static String getVluse(String jsonStr,String key) { //本方法大概耗時25納秒 char[] strs = jsonStr.toCharArray(); String result=""; for (int i = jsonStr.indexOf(key)+key.length()+1; i < strs.length; i++) { if (strs[i]!=','&&strs[i]!='}') { result+=strs[i]; }else { return result; } } return ""; } }