亲宝软件园·资讯

展开

详解分页组件中查count总记录优化

深邃老夏 人气:4
# 1 背景 研究mybatis-plus(以下简称MBP),使用其分页功能时。发现了一个[JsqlParserCountOptimize](https://gitee.com/-/ide/project/baomidou/mybatis-plus/edit/master/-/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/optimize/JsqlParserCountOptimize.java)的分页优化处理类,官方对其未做详细介绍,网上也未找到分析该类逻辑的只言片语,这情况咱也不敢用呀,索性深度剖析一下,也方便他人。 # 2 原理 首先PaginationInterceptor分页拦截器的原理这里不累述(mybatis通用分页封装的实现原理挺简单的,也就那么回事),最终落实到查询上基本是分为2个sql:查count总记录数 + 查真实分页记录。而此类是用优化来其中的查count这步。这count查询要怎么优化?这里上真实场景帮助大家理解: 假如有2张表user、user_address、user_account分别记录用户和用户地址和用户账户,1个用户可能有多个地址即1对多关系;1个用户只能有1个账户即1对1关系。 ## 2.1 优化order by 先看下面的sql,放到分页查询下 ``` select * from user order by age desc, update_time desc ``` 传统分页组件往往是 ``` 查count: select count(1) from (select * from user order by age desc, update_time desc) 查记录: select * from user order by age desc, update_time desc limit 0,50 ``` 发现问题了吗?查count时的order by是完全可以去掉的!在复杂查询、大表、非索引字段排序等情况下查记录已经很慢了,查count又要来一次!所以查count显然希望优化为`select count(1) from (select * from user)`。 ### 2.1.1 限制 但是也不是所有场景都可以优化的,比如带**group by**的查询 ### 2.1.2 源码 所以MBP源码如下实现,没有group by且有order by的语句,就把order by去掉 ``` // 添加包含groupBy 不去除orderBy if (null == groupBy && CollectionUtils.isNotEmpty(orderBy)) { plainSelect.setOrderByElements(null); sqlInfo.setOrderBy(false); } ``` ## 2.2 优化join场景 在join操作时,也存在优化可能,看下面sql ``` select u.id,ua.account from user u left join user_account ua on u.id=ua.uid ``` 这时候分页查count时,其实可以去掉left join直查user,因为user与user_account是1对1关系,如下 ``` 查count: select count(1) from user u 查记录: select u.id,ua.account from user u left join user_account ua on u.id=ua.uid limit 0,50 ``` ### 2.2.1 限制 查count能否去掉join直查首表,还存在诸多限制,如下: #### 表记录join后不能放大记录数 从上面案例可知,如果left join后记录数对比直查首表的总记录数会放大,就不能进行这个优化。比如3个用户每人各记录2条地址 ``` select u.id,ua.address from user u left join user_address ua on u.id=ua.uid (6条) vs select count(1) from user u (3条) ``` 此时去掉left join去查count就会得到更少的总记录数。**注意这可能会变成一个坑**,MBP无法自动判断本次分页查询是否会进行记录放大,所以join优化默认是关闭的,如果想开启需要声明自定义的JsqlParserCountOptimize bean,并设置optimizeJoin为true,如下 ``` @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; } ``` 其实这里源码设计有些不合理,因为开了之后就得小心翼翼的审查自己各类left join的分页代码了,如果有放大的话,只能构造Page对象时,设置optimizeCountSql为false(默认true),相当于关闭本次查询所有count优化,那么不光是join,包括order by等优化也都不进行了。建议可以改为从Page(或ThreadLocal?)中获取optimizeJoin,变为每次查询级别可配的配置,默认关,而经过开发人员确认可join优化的才主动在本次查询级别设置开启。 #### 仅限left join 如果是inner join或right join往往都会放大记录数,所以MBP优化会自动判断如果多个join里出现任何非left join的,就不进行此优化,比如`from a left join b .... right join c... left join d`此时会直接不进行优化 #### on语句有查询条件 比如 ``` select u.id,ua.account from user u left join user_account ua on u.id=ua.uid and ua.account > ? ``` #### where语句包含连接表的条件 比如 ``` select u.id,ua.account from user u left join user_account ua on u.id=ua.uid where ua.account > ? ``` ### 2.2.2 源码 MBP的join优化源码大致如下,对应上面的优化和限制 ``` List

加载全部内容

相关教程
猜你喜欢
用户评论