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的逻辑转换为直接定位数据的位置。

比如Mybatis 上的SQL语句为

<select id="queryUserListLikeName" parameterType="java.lang.String" resultType="com.entity.user">

select 
    <include refid="Base_Column_List" />
    from user t
    WHERE t.name LIKE '%${name}%' 
     order by id desc
  </select>

mybatis的 PageHelper 插件会在上面直接加上 limit 语句,源码如下

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();
    }

就是直接调用MySQL的分页limit函数。

如何mybatis的PageHelper插件能将我们的SQL语句改成如下,那就大大提高大表的翻页查询效率,我本人亲七万行数据的表分页到最后一页这种方式比直接limit的方式快10倍,更大的表效率更大,其实原理很简单,我们给查询结果集加一个行号,查询出ID,和行号,再和原表通过ID关联,因为关联走了索引,索引速度很快,然后直接通过行号定位数据,速度大大提高

select id, name from

(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 "";

}

}

MySQL高效分页-mybatis插件PageHelper改进的更多相关文章

  1. 使用Mybatis插件 PageHelper 模拟百度分页(Day_20)

    生活中,要学会沉淀生命,沉淀心情,沉淀自己 模拟百度分页最终实现效果如图: 本篇博客运行环境 JDK8 + IntelliJ IDEA 2018.3 + Tomcat 8.5.31 准备好了我们就开始 ...

  2. MySQL高效分页解决方案集(转)

    很久以前的一次面试中,被面试官问到这个问题,由于平时用到的分页方法不多,只从索引.分表.使用子查询精准定位偏移以外,没有使用到其它方法. 后来在看其它博客看到了一些不同的方案,也一直没有整理.今天有时 ...

  3. mysql高效分页方案及原理

    很久以前的一次面试中,被面试官问到这个问题,由于平时用到的分页方法不多,只从索引.分表.使用子查询精准定位偏移以外,没有使用到其它方法. 后来在看其它博客看到了一些不同的方案,也一直没有整理.今天有时 ...

  4. MySQL高效分页解决方案集

    一,最常见MYSQL最基本的分页方式: select * from content order by id desc limit 0, 10 在中小数据量的情况下,这样的SQL足够用了,唯一需要注意的 ...

  5. mysql 高效分页控件及c#调用实例

    第一.首先在mysql中创建一个存储过程 BEGIN /* @selectSql VARCHAR(5000), --sql语句 @orderWhere VARCHAR(200), --排序条件 @pa ...

  6. MySQL 高效分页

    create PROCEDURE USP_GetByPager( _pageindex int, _pagesize int ) BEGIN )*_pagesize; select * from A ...

  7. MySql高效分页SQL

    public string GetQuerySql(ITSPAreaQueryModel model, object state = null) { ); sqlBuilder.AppendForma ...

  8. Intellij IDEA 高效使用教程 (插件,实用技巧) 最好用的idea插件大全

    安装好Intellij idea之后,进行如下的初始化操作,工作效率提升十倍. 一. 安装插件 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句,这也太智能了,还显示了每条语句 ...

  9. Mybatis学习---Mybatis分页插件 - PageHelper

    1. Mybatis分页插件 - PageHelper说明 如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件. 该插件目前支持Oracle,Mysql,MariaDB,S ...

随机推荐

  1. django项目中使用KindEditor富文本编辑器。

    先从官网下载插件,放在static文件下 前端引入 <script type="text/javascript" src="/static/back/kindedi ...

  2. ES6 的class类 笔记

    class Person{ // 构造 constructor(x,y){ this.x = x; this.y = y; } toString(){ return (this.x + "的 ...

  3. runloop事件、UI更新、observer与coranimation

    一.触摸事件派发与视图绘制打包 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ __dispatchPreprocessedEve ...

  4. Ribbon自带负载均衡策略

    IRule这是所有负载均衡策略的父接口,里边的核心方法就是choose方法,用来选择一个服务实例. AbstractLoadBalancerRuleAbstractLoadBalancerRule是一 ...

  5. (3)使用Android手机作为树莓派的屏幕

    https://jingyan.baidu.com/album/676629977483b154d51b848e.html

  6. 【luogu 5395】 【模板】第二类斯特林数·行

    code: #include <bits/stdc++.h> #define ll long long #define setIO(s) freopen(s".in", ...

  7. typedi 强大的javascript以及typescript 依赖注入框架

    typedi 是typestack团队提供的依赖注入解决方案,对于typescript 我们可以使用注解的开发方式,官方的文档也比较详细 javascript 使用 基于函数的服务注入 var Ser ...

  8. LVS 的负载均衡调度算法

    LVS 的负载均衡调度算法 1.轮叫调度 (Round Robin) ( rr ) 调度器通过“ 轮叫 ”调度算法将外部请求按顺序轮流分配到集群的真实服务器上,它均等地对待每一台服务器,而不管服务器上 ...

  9. ZROI 2020WC集训训练赛 Day4

    最后一场,幸好没有掉分,假装功德圆满吧. T1有各种数据结构做法,主要难点在优化空间. T2直接DP就完事了,然而记搜的复杂度是对的-- T3神仙最小割. 没错这篇文章就是咕了. pkuwc rp++ ...

  10. sysmain服务怎么启动 & Win7 SuperFetch无法启动

    在控制面板/管理工具/服务中,只需找到Superfetch这个服务,双击,然后将其启动类型改为自动,并点击启动按钮并确定即可. Superfetch无法启动,系统找不到指定档案 ms-windows ...