当前使用版本(必填,否则不予处理)

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,到最后就会进入到ClientPreparedStatementgetCoreParameterIndex方法的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,到最后就会进入到ClientPreparedStatementgetCoreParameterIndex方法的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,到最后就会进入到ClientPreparedStatementgetCoreParameterIndex方法的checkBounds。 目前的解决方式是提供一个count方法设置page的countId,不执行sql优化的过程。 是否可以考虑,在判断优化join的过程中,考虑下如果left join中子查询有使用参数,就不优化left join了?

你有100年都没更新过mp版本了吧

额, 版本是3.4.1