目录

  • 前言
  • 分析
  • 源码
  • 总结

前言

PageHelper是一个很好用的分页插件,在这个插件中使用分页会执行两句sql

1、count语句的sql,因为分页需要总数
2、分页语句,使用分页参数进行服务端分页的sql

分析

我们在调用分页方法时正常会调用

PageHelper.startPage(1,10);

但是如果我们不需要分页总数时可以关闭count的sql语句查询,使用重载的方法即可

/*** 开始分页** @param pageNum  页码* @param pageSize 每页显示数量*/public static <E> Page<E> startPage(int pageNum, int pageSize) {return startPage(pageNum, pageSize, DEFAULT_COUNT);}/*** 开始分页** @param pageNum  页码* @param pageSize 每页显示数量* @param count    是否进行count查询*/public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {return startPage(pageNum, pageSize, count, null, null);}

调用

PageHelper.startPage(1,10,false);

本文要讲的是在PageHelper里面执行count时会对sql语句进行优化,而这个解析的过程中可能会存在一些解析失败的情况,导致sql语句报错

例如执行

select a.* from (table1 a,table2 b) where a.id = b.id;

正常情况下我们会觉得count的语句会是这样的

select count(*) from (select a.* from (table1 a,table2 b) where a.id = b.id) table;

但是实际上PageHelper会对count语句做一个优化

在我们报错的日志看sql是这样的

select count(0) from (select a.* from (table1 a table2 b) where a.id = b.id) table;

我们会发现table1 a,table2 b之间的逗号不见了,这就是解析失败的场景

那么如何才能关闭sql语句的解析优化呢,我们从源码开始了解下

源码

我们从PageHelper的核心开始看,先看PageInterceptor类,这个类是使用的Mybatis插件来实现的,插件机制这里不做分析,可以看这篇

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),}
)
public class PageInterceptor implements Interceptor {private volatile Dialect dialect;private String countSuffix = "_COUNT";protected Cache<String, MappedStatement> msCountMap = null;private String default_dialect_class = "com.github.pagehelper.PageHelper";@Overridepublic Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//由于逻辑关系,只会进入一次if (args.length == 4) {//4 个参数时boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 个参数时cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}checkDialectExists();List resultList;//调用方法判断是否需要进行分页,如果不需要,直接返回结果if (!dialect.skip(ms, parameter, rowBounds)) {//判断是否需要进行 count 查询if (dialect.beforeCount(ms, parameter, rowBounds)) {//查询总数// 核心代码Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);//处理查询总数,返回 true 时继续分页查询,false 时直接返回if (!dialect.afterCount(count, parameter, rowBounds)) {//当查询总数为 0 时,直接返回空的结果return dialect.afterPage(new ArrayList(), parameter, rowBounds);}}resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}return dialect.afterPage(resultList, parameter, rowBounds);} finally {dialect.afterAll();}}

看核心代码处的count方法

private Long count(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {String countMsId = ms.getId() + countSuffix;Long count;//先判断是否存在手写的 count 查询MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);if (countMs != null) {count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);} else {countMs = msCountMap.get(countMsId);//自动创建if (countMs == null) {//根据当前的 ms 创建一个返回值为 Long 类型的 mscountMs = MSUtils.newCountMappedStatement(ms, countMsId);msCountMap.put(countMsId, countMs);}// 核心代码count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);}return count;}

看executeAutoCount方法

public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,Object parameter, BoundSql boundSql,RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);//创建 count 查询的缓存 keyCacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);//调用方言获取 count sql// 核心代码String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);//countKey.update(countSql);BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中for (String key : additionalParameters.keySet()) {countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}//执行 count 查询Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);Long count = (Long) ((List) countResultList).get(0);return count;}

看getCountSql方法,到了PageHelper的getCountSql

@Overridepublic String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);}

AbstractHelperDialect的getCountSql方法

public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {Page<Object> page = getLocalPage();String countColumn = page.getCountColumn();if (StringUtil.isNotEmpty(countColumn)) {return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);}// 核心代码return countSqlParser.getSmartCountSql(boundSql.getSql());}

调用了countSqlParser.getSmartCountSql

public String getSmartCountSql(String sql) {return getSmartCountSql(sql, "0");
}
public String getSmartCountSql(String sql, String name) {//解析SQLStatement stmt = null;//特殊sql不需要去掉order by时,使用注释前缀// 核心代码if(sql.indexOf(KEEP_ORDERBY) >= 0){return getSimpleCountSql(sql);}try {stmt = CCJSqlParserUtil.parse(sql);} catch (Throwable e) {//无法解析的用一般方法返回count语句return getSimpleCountSql(sql);}Select select = (Select) stmt;SelectBody selectBody = select.getSelectBody();try {//处理body-去order byprocessSelectBody(selectBody);} catch (Exception e) {//当 sql 包含 group by 时,不去除 order byreturn getSimpleCountSql(sql);}//处理with-去order byprocessWithItemsList(select.getWithItemsList());//处理为count查询sqlToCount(select, name);String result = select.toString();return result;}

看到了上面的核心代码

     if(sql.indexOf(KEEP_ORDERBY) >= 0){return getSimpleCountSql(sql);}try {stmt = CCJSqlParserUtil.parse(sql);} catch (Throwable e) {//无法解析的用一般方法返回count语句return getSimpleCountSql(sql);}

KEEP_ORDERBY定义

public static final String KEEP_ORDERBY = "/*keep orderby*/";

也就是当sql中包含/*keep orderby*/字符串时会走getSimpleCountSql

public String getSimpleCountSql(final String sql, String name) {StringBuilder stringBuilder = new StringBuilder(sql.length() + 40);stringBuilder.append("select count(");stringBuilder.append(name);stringBuilder.append(") from (");stringBuilder.append(sql);stringBuilder.append(") tmp_count");return stringBuilder.toString();}

getSimpleCountSql也就是简单的对sql做了个count的拼接

而另外的情况就会调用CCJSqlParserUtil.parse(sql),CCJSqlParserUtil类是jsqlparser-1.2.jar的一个工具类,用于对sql进行解析的,也就是会把我们的sql做一个优化解析。

但是例如我们上述这种场景,我们的sql在mysql服务端是可以正常执行的,但是被优化后就不能正常执行了,这个时候我们只要在我们的sql中加上/*keep orderby*/即可使用简单的count拼接了

总结

PageHelper在解析优化count语句时可能不能理解我们的语义,导致解析后不是我们需要的sql,这个时候我们就只能想办法关闭解析优化了

/*keep orderby*/是一个注释,实际上不会对我们的sql执行造成影响,并且这种方法也不是PageHelper提供给我们的关闭count语句优化的方法,但是基于源码,我们这样做确实可以解决问题

可以看到PageHelper的源码看起来还是相对简单的,并且注释也是中文的,看起来并不费劲。这里就是对自己使用过程中出现问题解决方案的一个总结了

PageHelper关闭count语句优化相关推荐

  1. PageHelper 关闭COUNT(0)查询 以及PageHelper 的分页原理分析

    pagehelper 关闭count(0)查询 以及pagehelper的分页原理分析 情景再现:在给移动端提供分页查询数据接口时,知道他们不需要总条数.但是使用pagehelper 分页查询打印的s ...

  2. mysql函数做条件_MySQL语句优化(三):避免条件字段做函数操作

    今天跟各位分享一个生产环境慢查询的例子,是一个比较典型的"条件字段使用了函数导致无法走索引"的例子. 一.定位慢查询 首先发现慢查询告警,通过运维平台看到慢查询主要是下面这条:SE ...

  3. MYSQL语句优化:limit和count的优化

    正 文: SQL语句的优化大有学问,不同的写法取得的效果大为不同.今例举limit和count语句来作下探讨 1, limit语句的优化 . 常见的limit语句的形式为:LIMIT m,n;随之偏移 ...

  4. oracle 语法分析表,Oracle 语句优化分析说明

    Oracle 语句优化分析说明 更新时间:2009年09月17日 21:52:20   作者: Oracle 语句优化技巧,大家可以参考使用,使你的oracle运行效率更高更好. 1. ORACLE ...

  5. mysql优化的几种方法_详解mysql数据库不同类型sql语句优化方法

    概述 分享一下之前笔记记录的一些不同类型sql语句优化方法,针对mysql. 主要分成优化INSERT语句.优化ORDER BY语句.优化GROUP BY 语句.优化嵌套查询.优化OR语句这几个方面, ...

  6. MySQL优化之三:SQL语句优化

    一 SQL语句优化的一般步骤: 1 通过show status命令了解各种SQL语句的执行频率 mysql> show status;                #show status:显 ...

  7. SQL语句优化(落实到代码,不绕弯子)

    文章目录 看完本篇文章你能学到什么? SQL语句优化 1.1 排序优化 1.1.1 索引优化 1.1.2 算法优化 1.1.3 排序优化建议 1.2 分组优化 1.3 分页优化 1.3.1 分页优化一 ...

  8. mysql 亿级表count_码云社 | 砺锋科技-MySQL的count(*)的优化,获取千万级数据表的总行数 - 用代码改变世界...

    专注于Java领域优质技术号,欢迎关注 作者:李长念 一.前言 这个问题是今天朋友提出来的,关于查询一个1200w的数据表的总行数,用count(*)的速度一直提不上去.找了很多优化方案,最后另辟蹊径 ...

  9. mysql的count(*)的优化,获取千万级数据表的总行数

    一.前言 这个问题是今天朋友提出来的,关于查询一个1200w的数据表的总行数,用count(*)的速度一直提不上去.找了很多优化方案,最后另辟蹊径,选择了用explain来获取总行数. 二.关于cou ...

  10. mysql innodb count_MySQL下INNODB引擎的SELECT COUNT(*)性能优化及思考

    正 文: MySQL下INNODB引擎的SELECT COUNT(*)性能优化及思考 最近有项目有高并发需求,服务器采用负载均衡,数据库采用阿里云的RDS MYSQL,16核64G内存,连接数:160 ...

最新文章

  1. vue 加载体验优化
  2. nn.Upsampling is deprecated. Use nn.functional.interpolate instead.
  3. c语言行计数程序,C语言非常简单的字符统计程序50行
  4. Silverlight+WCF 新手实例 象棋 主界面-事件区-返回退出(三十三)
  5. 浅析WebRtc中视频数据的收集和发送流程
  6. php任务分配思路_PHP执行定时任务的几种方法思路
  7. tableView 获取网络图片,并且设置为圆角(优化,fps)
  8. 关于K-Means算法
  9. “明年AI会如何?”英伟达问了13位不同行业的专家
  10. 95 后程序员一出校门就拿年薪 30多万?
  11. beanstalkd最佳实践-编程开发
  12. 锁失效_分布式锁的解决方案(二)
  13. js基础-24-伪数组转化为真数组
  14. 海词词典android v3.1.2新版发布 英语学习必备工具,海词词典Android V3.1.2新版发布 英语学习必备工具...
  15. 从0到1的电商架构应该怎么做?
  16. 桌面图标拖不动怎么办?
  17. vs2012中将图片放到resource中进行调用
  18. python 读写csv文件(创建、追加、覆盖)_python文件操作
  19. 520套日式 韩游 消除RGB类 人物角色 动画序列帧 手游动画
  20. linux查询日志命令加过滤,日志查看技巧之筛选[linux命令集][排查篇]

热门文章

  1. 关于java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)的问题解决办法。(相对路径出错)
  2. JAVA实现邮件抄送,密送,多个附件发送
  3. Mac 全局安装 webpack 报错权限不足解决方法
  4. 总所周知,Github是一个读小说的网站!《Re0:从零开始的异世界生活》Web版
  5. 几行代码,把你的小电影全部藏好了!
  6. 漫反射贴图与镜面光贴图
  7. 达梦数据库报网络通讯异常排查步骤
  8. 用c语言编程小鸭子,小鸭子
  9. OkHttp简单封装
  10. Markdown语法-表格内换行