当前使用版本

3.4.3.4

该问题是如何引起的?

该问题是从 3.1.1 版本升级到 3.4.3.4 版本后回归测试业务时出现的。为避免不必要的干扰,以下信息已做业务脱敏,仅还原关键信息。

重现步骤

有一张表 t1, 其中两个字段 A/B 具有优先关系,按照业务需求,A不为空时优先取A值,否则为B值。

考虑以下 伪SQL (数据库为 MySQL,支持 IFNULL 函数):

SELECT IFNULL(ColumnA, ColumnB) AS ColumnC
FROM t1
WHERE ...
ORDER BY ColumnC

使用 Mybatis Plus 分页插件,且不开启分页优化(page.setOptimizeCountSql(false)),生成的 SQL 如下:

SELECT COUNT(*) FROM (
SELECT IFNULL(ColumnA, ColumnB) ColumnC FROM t1 WHERE ... ORDER BY ColumnC
) TOTAL

这样的 SQL 没有问题,程序正常运行。

但开启优化(即默认情况下)时 SQL如下:

SELECT COUNT(*) AS total FROM t1 WHERE ... ORDER BY ColumnC

由于 ColumnC 实际上是 IFNULL 函数列的别名,原表 t1 中并不包含该字段,这样的 SQL 将引发 SQL 语法异常。

鉴于 order by 子句对 Count 的结果没有影响,建议在开启优化的时候不拼接 order by 子句,以避免列别名导致的类似错误。

报错信息

Cause: java.sql.SQLSyntaxErrorException: Unknown column 'StartDate' in 'order clause' ; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'StartDate' in 'order clause' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:239) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) at com.sun.proxy.$Proxy123.selectList(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224) at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForIPage(MybatisMapperMethod.java:121) at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:85) at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148) at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89) at com.sun.proxy.$Proxy263.listBudgetRules(Unknown Source) (省略更多堆栈信息)

Comment From: miemieYaho

3.1.1没这问题吗?

Comment From: Muyangmin

是的,没有

Comment From: Muyangmin

我简单看了一下 3.0 分支上的 PaginationInnerInterceptor 代码,在 autoCountSql 方法里面找到这样一段:

 if (CollectionUtils.isNotEmpty(orderBy)) {
                boolean canClean = true;
                if (groupBy != null) {
                    // 包含groupBy 不去除orderBy
                    canClean = false;
                }
                if (canClean) {
                    for (OrderByElement order : orderBy) {
                        // order by 里带参数,不去除order by
                        Expression expression = order.getExpression();
                        if (!(expression instanceof Column) && expression.toString().contains(StringPool.QUESTION_MARK)) {
                            canClean = false;
                            break;
                        }
                    }
                }
                if (canClean) {
                    plainSelect.setOrderByElements(null);
                }
            }

我没有仔细研读过 mp 的代码和沿革,不清楚这里为什么需要这样的逻辑。但既然有对参数的判断,就补充一点信息(不清楚是否对复现和修复有用,仅供参考) :在我们实际业务中发生异常的SQL代码的 order by 子句是带参数的,类似于这样:

ORDER BY IF(XXX=?,0,1), StartDate ASC, YYY ASC

其中 StartDate 为前文示例中的别名字段。

Comment From: miemieYaho

带参数下不会去除order by,否则会报错

Comment From: Muyangmin

对,我看到了这个逻辑,但这是为什么,方便解释一下吗?

Comment From: miemieYaho

sql里有几个问号就得有几个入参

Comment From: Muyangmin

那么,新版本下对于这种场景是否只能关闭 count 优化?

Comment From: miemieYaho

看看 page.setCountId