当前使用版本
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