分表分页查询:版本冲突导致分表情况下的分页查询异常问题-Must have sharding column with subquery

版本冲突导致分表情况下的分页查询异常问题

前提须知

  1. 首先明确一点,如果子查询中有分表的查询,同时没有将分表依据的字段作为查询参数,会导致无法进行分表查询处理,即抛出异常
  2. mybatisplus 分页查询流程,先查数据总数,再查分页数据

问题描述

  1. 当不传分表字段作为参数查询时,查询报错,报错信息如下
Must have sharding column with subquery.

意思就是查询参数缺少分表依据的字段。
2. 传分表字段查询,可以正常分页查询。
3. 但是,对于不传分表字段的情况,正常查询过程是扫描全部分表进行查询,然后汇总。
4. 因此,不传分表字段应该是没有问题的。

分析问题

  1. 当查询参数有传分表依据的字段数据,是能正常分页查询的,因此分表查询没有问题。
  2. 检查分页查询的sql,因为使用的是mybatis-plus,不用写mapper.xml
    因此,需要将sql语句进行打印。

寻找问题

  1. 开启查询语句的debug日志级别,
添加启动参数
-Dlogging.level.com.*.*.*Mapper=DEBUG
  1. 发现sql语句问题
SELECT COUNT(1) FROM ( SELECT * FROM t_active_user_award WHERE state IN (?,?,?,?)  ORDER BY create_time DESC ) TOTAL 
  1. 在计算total时,生成的语句是含有子查询的,正常情况sql应该是不含子查询的
  2. debug 寻找 查询总数的sql再哪组装。
  • PaginationInterceptor类
 public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        this.sqlParser(metaObject);
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()) {
            BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
            Object paramObj = boundSql.getParameterObject();
            IPage<?> page = (IPage)ParameterUtils.findPage(paramObj).orElse((Object)null);
            if (null != page && page.getSize() >= 0L) {
                if (this.limit > 0L && this.limit <= page.getSize()) {
                    this.handlerLimit(page);
                }

                String originalSql = boundSql.getSql();
                Connection connection = (Connection)invocation.getArgs()[0];
                if (page.isSearchCount() && !page.isHitCount()) {
                // 这里进行 sql 组装
                    SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql, metaObject);
                    // 此处进行total查询
                    this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
                    if (!this.continueLimit(page)) {
                        return null;
                    }
                }

              ...省略
                return invocation.proceed();
            } else {
                return invocation.proceed();
            }
        } else {
            return invocation.proceed();
        }
    }

进行 sql 组装

SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql, metaObject);

组装过程


public class JsqlParserCountOptimize implements ISqlParser {
    ...省略

    @Override
    public SqlInfo parser(MetaObject metaObject, String sql) {
        if (logger.isDebugEnabled()) {
            logger.debug("JsqlParserCountOptimize sql=" + sql);
        }
        SqlInfo sqlInfo = SqlInfo.newInstance();
        try {
            Select selectStatement = (Select) CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
            Distinct distinct = plainSelect.getDistinct();
            // 1. 由于PlainSelect所属 jsqlpaarser.jar jar包版本过低,没有getGroupBy此方法,此处抛出异常
            GroupByElement groupBy = plainSelect.getGroupBy();
            List<OrderByElement> orderBy = plainSelect.getOrderByElements();

           ...省略
            // 2.优化 SQL,正常的select count(*) from table where ....
            plainSelect.setSelectItems(COUNT_SELECT_ITEM);
            return sqlInfo.setSql(selectStatement.toString());
        } catch (Throwable e) {
            // e = net.sf.jsqlparser.statement.select.PlainSelect.getGroupBy()Lnet/sf/jsqlparser/statement/select/GroupByElement;
            // 无法优化使用原 SQL
            // 3. 返回的是有子查询的sql select count(*) from (select * from table where)
            return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(sql));
        }
    }
}

异常

java.lang.NoSuchMethodError: net.sf.jsqlparser.statement.select.PlainSelect.getGroupBy()Lnet/sf/jsqlparser/statement/select/GroupByElement;

低版本jsqlpaarser-0.9.5.jar中,没有plainSelect.getGroupBy()方法,导致抛出异常,无法优化原SQL。

最后组装的sql结果为

SELECT COUNT(1) FROM ( SELECT * FROM table WHERE state IN (?,?,?,?)  ORDER BY create_time DESC ) TOTAL 

而想要的查询总数的语句

SELECT COUNT(1) FROM table WHERE state IN (?,?,?,?)

那么分页查询异常的原因就是如下
因为当子查询是分表查询语句时,不传分表依据字段,就会抛出异常

寻找jsqlpaarser包依赖问题,

在这里插p入图片描述版本冲突选择了低版本的包

处理问题:排除低版本的包。

那么maven依赖包冲突如何自动解决的呢

maven解决版本冲突原则

maven解决版本冲突主要是两个原则。

  • 路径最短原则

  • 优先声明原则:当出现路径长短相同的时候,谁先声明就用谁。

经过排查,发现本次问题导致采用了优先声明原则,因此,以后对于基础包等重要的包需要提前声明,而依赖的业务客户端包得放在最后。

到此问题就正常解决了,此次问题最终原因还是依赖问题。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多多洛码代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值