1 background
When using the paging function of mybatis plus (hereinafter referred to as MBP). Found a JsqlParserCountOptimize The official didn't give a detailed introduction to the paging optimization processing class of, and didn't find a few words on the Internet to analyze the logic of this class, which we dare not use. It's also convenient for others to analyze in depth.
2 Principle
First of all, the principle of PaginationInterceptor is not covered here (the implementation principle of mybatis general pagination encapsulation is quite simple, which is the same thing). Finally, the query is basically divided into two sql: total count records + real pagination records. This class uses optimization to check the count. How to optimize the count query? Here's a real scenario to help you understand: if there are two tables, user, user address and user account, which record user and user address and user account respectively, a user may have multiple addresses, i.e. 1-to-many relationship; a user can only have one account, i.e. 1-to-1 relationship.
2.1 optimize order by
First look at the following sql and put it under paging query
select * from user order by age desc, update_time desc
Traditional paging components are often
check count: select count(1) from (select * from user order by age desc, update_time desc) //Record: select * from user order by age desc, update_time desc limit 0,50
Any problems? The order by can be completely removed when checking the count! In the case of complex query, large table, sorting of non index fields and so on, it's very slow to query records. Count again! Therefore, it is obvious that you want to optimize count to select count(1) from (select * from user).
2.1.1 restriction
But not all scenarios can be optimized, such as queries with group by
2.1.2 source code
So the MBP source code is implemented as follows. If there is no group by and order by statement, remove the order by
// Add include groupBy without removing orderBy if (null == groupBy && CollectionUtils.isNotEmpty(orderBy)) { plainSelect.setOrderByElements(null); sqlInfo.setOrderBy(false); }
2.2 optimize the join scenario
In the join operation, there is also the possibility of optimization. See the following sql
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid
In this case, you can delete the left join direct query user when querying the count page by page, because the relationship between user and user account is 1-to-1, as follows
check count: select count(1) from user u //Record: select u.id,ua.account from user u left join user_account ua on u.id=ua.uid limit 0,50
2.2.1 restriction
There are many restrictions on whether count can remove the join direct query of the first table, as follows:
The number of records cannot be enlarged after the table records the join
From the above case, if the number of records after left join is larger than the total number of records in the first direct query table, this optimization cannot be carried out. For example, three users each record two addresses
select u.id,ua.address from user u left join user_address ua on u.id=ua.uid (6 Strip) vs select count(1) from user u (3 Strip)
In this case, if you remove the left join and check the count, you will get less total records. Note that this may turn into a join. MBP cannot automatically determine whether the paging query will be record enlarged. Therefore, optimizeJoin is off by default. If you want to open the JsqlParserCountOptimize bean that needs to be declared by yourself, and set optionjoin to true, as follows
@Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; }
In fact, the source code design here is somewhat unreasonable, because after opening, you have to carefully review the paging codes of your various left join s. If there is amplification, when you can only construct Page objects, set the optimizeCountSql to false (default true), which is equivalent to turning off all count optimizations in this query. Then not only join, but also order by and other optimizations will not be carried out. It is recommended to obtain the optimizeJoin from Page (or ThreadLocal?) instead, and change it to the configuration that can be configured for each query level, which is off by default. Only after the developer confirms that the join can be optimized can the active setting of this query level be turned on.
left join only
If it is an inner join or a right join, the number of records will always be enlarged, so MBP optimization will automatically judge that if there is any non left join in multiple joins, it will not be optimized. For example, from a left join b.. Right join c.. Left join D will not be optimized directly at this time
on statement has query conditions
such as
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid and ua.account > ?
where statement contains conditions for joining tables
such as
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid where ua.account > ?
2.2.2 source code
MBP's join optimization source code is roughly as follows, corresponding to the above optimization and limitations
List<Join> joins = plainSelect.getJoins(); // Whether optimizeJoin is enabled globally (it is recommended that you can also set it from Page by each query) if (optimizeJoin && CollectionUtils.isNotEmpty(joins)) { boolean canRemoveJoin = true; String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY); for (Join join : joins) { // left join only if (!join.isLeft()) { canRemoveJoin = false; break; } Table table = (Table) join.getRightItem(); String str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + StringPool.DOT; String onExpressionS = join.getOnExpression().toString(); /* If the join contains? (represents that the on statement has query conditions) perhaps where Statement contains conditions for joining tables Do not remove the join */ if (onExpressionS.contains(StringPool.QUESTION_MARK) || whereS.contains(str)) { canRemoveJoin = false; break; } } if (canRemoveJoin) { plainSelect.setJoins(null); } }
2.3 optimize the location of select count(1)
In traditional pagination, select count(1) is often set in the outer layer of the original query sql, such as
select count(1) from (select * from user)
The real purpose of count is to get the number of records, which does not need the extra time of select * in the original query, so it can be optimized as follows
select count(1) from user
2.3.1 restriction
In the same way, there are some scenarios where count location cannot be optimized
The field of select contains parameters
This optimization can not be performed if the select contains parameters {}, ${} waiting to be replaced, because the subsequent placeholder replacement real value stage will result in an error due to the decrease of the number of placeholders, such as
select count(1) from (select power(#{aSelectParam},2) from user_account where uid=#{uidParam}) ua vs select count(1) from user_account where uid=#{uidParam} ua
MBP official issue Chen 95 registered this issue
Include distinct
select contains the statement of distinct de duplication, which may increase the number of count records, so this optimization cannot be carried out. such as
select count(1) from (select distinct(uid) from user_address) ua vs select count(1) from user_address ua #Number of records may increase
Include group by
For statements containing group by, because there are always aggregation functions in select, the built-in semantics of count(1) becomes aggregation functions, which cannot be optimized. such as
select count(1) from (select uid,count(1) from user_address group by uid) ua #Return the total records of single row and single column vs select count(1) from user_address group by uid #Return the number of aggregate count of multiple rows and single columns
2.3.2 source code
The related source code in MBP is as follows
//The parameter contained in the field of select is not optimized for (SelectItem item : plainSelect.getSelectItems()) { if (item.toString().contains(StringPool.QUESTION_MARK)) { return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(selectStatement.toString())); } } // Include distinct, groupBy not optimized if (distinct != null || null != groupBy) { return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(selectStatement.toString())); } ... // To optimize SQL, count select item is actually a select count(1) statement plainSelect.setSelectItems(COUNT_SELECT_ITEM);
3 Summary
In fact, this paper aims at some optimization ideas for the step of checking the number of count records in the general paging component
- Optimize order by
- Optimize join statement
- Optimize the location of select count(1)
- Pay attention to the restrictions corresponding to the above optimization, otherwise business errors may be caused (especially the join optimization, which is relatively hidden)
In fact, it's not limited to MBP. You can also try to use the customized page blocker, which has a significant effect on page optimization
"Used to record the evolution of life, the iteration of story. Looking forward to be a platform to help and think for you