当前使用版本(必填,否则不予处理)
3.5.5
该问题是如何引起的?(确定最新版也有问题再提!!!)
mysql分页优化 join时出现的问题。
重现步骤(如果有就写完整)
原sql为:
SELECT
ps.*,
pcp.company_name,
pl.id,
pl.parking_lot_name
FROM
parking_space AS ps
INNER JOIN parking_lot_space_scope AS plss_condition ON plss_condition.parking_space_id = ps.id
AND plss_condition.parking_lot_id = ?
LEFT JOIN parking_company AS pcp ON pcp.id = ps.parking_company_id
LEFT JOIN parking_lot_space_scope AS plss ON plss.parking_space_id = ps.id
LEFT JOIN parking_lot AS pl ON pl.id = plss.parking_lot_id
WHERE
ps.parking_company_id = ?
ORDER BY
ps.parking_space_number;
分页优化后的sql为:
SELECT
count(*)
FROM
parking_space AS ps
INNER JOIN parking_lot_space_scope AS plss_condition ON plss_condition.parking_space_id = ps.id
AND plss_condition.parking_lot_id = ?
LEFT JOIN parking_company AS pcp ON pcp.id = ps.parking_company_id
LEFT JOIN parking_lot_space_scope AS plss ON plss.parking_space_id = ps.id
LEFT JOIN parking_lot AS pl ON pl.id = plss.parking_lot_id
WHERE
ps.parking_company_id = ?
报错信息
在上述分页后的sql中,还是即使left join 没有出现在where条件中,但是在分页时依旧被保留了,我看你代码中是这样写的:
if (optimizeJoin && page.optimizeJoinOfCountSql()) {
List<Join> joins = plainSelect.getJoins();
if (CollectionUtils.isNotEmpty(joins)) {
boolean canRemoveJoin = true;
String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY);
// 不区分大小写
whereS = whereS.toLowerCase();
for (Join join : joins) {
if (!join.isLeft()) {
canRemoveJoin = false;
break;
}
FromItem rightItem = join.getRightItem();
String str = "";
if (rightItem instanceof Table) {
Table table = (Table) rightItem;
str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + StringPool.DOT;
} else if (rightItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) rightItem;
/* 如果 left join 是子查询,并且子查询里包含 ?(代表有入参) 或者 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
if (subSelect.toString().contains(StringPool.QUESTION_MARK)) {
canRemoveJoin = false;
break;
}
str = subSelect.getAlias().getName() + StringPool.DOT;
}
// 不区分大小写
str = str.toLowerCase();
if (whereS.contains(str)) {
/* 如果 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
canRemoveJoin = false;
break;
}
for (Expression expression : join.getOnExpressions()) {
if (expression.toString().contains(StringPool.QUESTION_MARK)) {
/* 如果 join 里包含 ?(代表有入参) 就不移除 join */
canRemoveJoin = false;
break;
}
}
}
if (canRemoveJoin) {
plainSelect.setJoins(null);
}
}
}
你这段代码是将全部join遍历,如果所有join都是left join,并且都不存在于where条件中时,就将所有的join都移除掉,但是按我的理解,应该是在遍历join的时候,只要有一个不存在于where条件中的left join,就应该移除掉才对,请问作者这样做的目的是什么呢?
Comment From: binfengyan
这个不是缺陷,首先你的理解是有问题的left join 会存在一对多的情况,根据where条件去剔除join的表是不合理的,建议先好好理解join的作用
Comment From: miemieYaho
代码的意思是如果遇到xxx就不移除join(canRemoveJoin = false;break;),移除join没写那么细,现在是要么都移除要么都不移除
Comment From: binfengyan
代码的意思是
如果遇到xxx就不移除join(canRemoveJoin = false;break;),移除join没写那么细,现在是要么都移除要么都不移除
他的意思好像就是where条件中没有与这个join表相关字段的条件,就移除这个join表,事实上可能存在join中一对多的情况,这样的移除反而影响数据正确性了
Comment From: PeiyangChina
代码的意思是
如果遇到xxx就不移除join(canRemoveJoin = false;break;),移除join没写那么细,现在是要么都移除要么都不移除他的意思好像就是where条件中没有与这个join表相关字段的条件,就移除这个join表,事实上可能存在join中一对多的情况,这样的移除反而影响数据正确性了
是的,如果在数据库确实设计的是一对多的关系,而且业务上也是这样定义的,需要join后不做where筛选,就要展示多条数据,那我建议可以使用Page的API:setOptimizeJoinOfCountSql(final boolean optimizeJoinOfCountSql),来关闭优化join
Comment From: muzinian
还有一个问题,如果left join是一个子查询,子查询内部会设置参数,比如#{xxxx}这种,查询结果即使有表名,但是如果where条件中没有这个子查询条件,就回优化掉left join。而这会导致优化后count语句设置参数的时候失败。举个例子
select y.field1,y.field2,x.filed1,x.filed2
from xxx x
left join
(
SELECT filed1,filed2
FROM yyy
where is_deleted = 0
and filed3 = #{param.filed3}
and filed4 between #{param.begin} and #{param.end}
and filed5 in
<foreach collection="param.list" item="id" index="index" open="(" close=")" separator=",">
#{id}
</foreach>
) y on y.filed1 = x.filed1
where x.filed3 = #{param.filed3}
这样,sql最终优化出来的count语句就是:
select count(1)
from xxx x
where x.filed3 = #{param.filed3}
这里,就出现了问题。后续代码在执行这个count语句时候,会使用查询语句的参数列表,也就是sql中param的参数,设置count语句的参数,这就会导致判断索引的时候超出了。
https://github.com/baomidou/mybatis-plus/blob/7847987d59d2ea4b29d3f1d66c0091f641e6c1d5/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/PaginationInnerInterceptor.java#L135
这里执行count语句。最终会调用mybatis的某一个typeHandler来调用setParameters,到最后就会进入到ClientPreparedStatement的getCoreParameterIndex方法的checkBounds。
目前的解决方式是提供一个count方法设置page的countId,不执行sql优化的过程。
是否可以考虑,在判断优化join的过程中,考虑下如果left join中子查询有使用参数,就不优化left join了?
Comment From: miemieYaho
还有一个问题,如果left join是一个子查询,子查询内部会设置参数,比如
#{xxxx}这种,查询结果即使有表名,但是如果where条件中没有这个子查询条件,就回优化掉left join。而这会导致优化后count语句设置参数的时候失败。举个例子
sql select y.field1,y.field2,x.filed1,x.filed2 from xxx x left join ( SELECT filed1,filed2 FROM yyy where is_deleted = 0 and filed3 = #{param.filed3} and filed4 between #{param.begin} and #{param.end} and filed5 in <foreach collection="param.list" item="id" index="index" open="(" close=")" separator=","> #{id} </foreach> ) y on y.filed1 = x.filed1 where x.filed3 = #{param.filed3}这样,sql最终优化出来的
count语句就是:
sql select count(1) from xxx x where x.filed3 = #{param.filed3}这里,就出现了问题。后续代码在执行这个
count语句时候,会使用查询语句的参数列表,也就是sql中param的参数,设置count语句的参数,这就会导致判断索引的时候超出了。https://github.com/baomidou/mybatis-plus/blob/7847987d59d2ea4b29d3f1d66c0091f641e6c1d5/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/PaginationInnerInterceptor.java#L135
这里执行
count语句。最终会调用mybatis的某一个typeHandler来调用setParameters,到最后就会进入到ClientPreparedStatement的getCoreParameterIndex方法的checkBounds。 目前的解决方式是提供一个count方法设置page的countId,不执行sql优化的过程。 是否可以考虑,在判断优化join的过程中,考虑下如果left join中子查询有使用参数,就不优化left join了?
你有100年都没更新过mp版本了吧
Comment From: muzinian
还有一个问题,如果left join是一个子查询,子查询内部会设置参数,比如
#{xxxx}这种,查询结果即使有表名,但是如果where条件中没有这个子查询条件,就回优化掉left join。而这会导致优化后count语句设置参数的时候失败。举个例子sql select y.field1,y.field2,x.filed1,x.filed2 from xxx x left join ( SELECT filed1,filed2 FROM yyy where is_deleted = 0 and filed3 = #{param.filed3} and filed4 between #{param.begin} and #{param.end} and filed5 in <foreach collection="param.list" item="id" index="index" open="(" close=")" separator=","> #{id} </foreach> ) y on y.filed1 = x.filed1 where x.filed3 = #{param.filed3}这样,sql最终优化出来的
count语句就是:sql select count(1) from xxx x where x.filed3 = #{param.filed3}这里,就出现了问题。后续代码在执行这个
count语句时候,会使用查询语句的参数列表,也就是sql中param的参数,设置count语句的参数,这就会导致判断索引的时候超出了。 https://github.com/baomidou/mybatis-plus/blob/7847987d59d2ea4b29d3f1d66c0091f641e6c1d5/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/PaginationInnerInterceptor.java#L135这里执行
count语句。最终会调用mybatis的某一个typeHandler来调用setParameters,到最后就会进入到ClientPreparedStatement的getCoreParameterIndex方法的checkBounds。 目前的解决方式是提供一个count方法设置page的countId,不执行sql优化的过程。 是否可以考虑,在判断优化join的过程中,考虑下如果left join中子查询有使用参数,就不优化left join了?你有100年都没更新过mp版本了吧
额, 版本是3.4.1