上线完成后,巡检日志。发现druid报了一个slow sql的错
ERROR c.a.druid.filter.stat.StatFilter - slow sql 1909 millis.
看了下,发现这个sql有些不一样:
select id, biz_filed_1from table1WHERE status IN (?, ?) AND biz_date IS NOT NULL
AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id > ? AND id因为id是主键,经过mysql优化器的处理,这种sql的执行结果也是对的。
但是,MySQL默认sql语句最大为1M。如果不解决,当超过这个限制时,就报错了
问题的范围应该在MyBatis-Plus的条件构造器。拼sql使用的是MyBatis-Plus的查询条件构造器QueryWrapper。涉及到代码如下:
public class BizDataService {
public void doTask() {
log.info("任务开始");
QueryWrapper<BizData> queryWrapper = new QueryWrapper<>();
queryWrapper.in("status", 1, 2, 3);
queryWrapper.isNotNull("biz_filed_1");
queryWrapper.select("id", "biz_filed_1");
queryWrapper.orderByAsc("id");
queryWrapper.last("limit 10 ");
List<BizData> bizDataList = bizDataService.list(queryWrapper); if (CollectionUtils.isEmpty(bizDataList)) {
log.info("没有满足条件的数据"); return;
} while (true) {
updateData(traceId, bizDataList); if (CollectionUtils.isEmpty(bizDataList)) {
log.info("任务 完成 "); return;
}
Long lastId = bizDataList.get(bizDataList.size() - 1).getId();
log.info(" lastId {} ", lastId);
queryWrapper.gt("id", lastId);
bizDataList = bizDataService.list(queryWrapper);
}
}
}有问题的sql,应该出现在构建 id>? 环节 :
queryWrapper.gt("id",lastId);因为要取已经处理过的最大的id,所以放在while循环中了。为了复用,直接使用了方法最开始的条件构造器queryWrapper。结合上面的慢sql,很可能是MyBatis-Plus拼sql的条件构造器没有做去重处理。 不过单从上面这个场景来看,MyBatis-Plus作为基础框架也不知道应该保留那一次,最多能做的是遇到重复的做个去重。
case :
@Slf4j@SpringBootTest(classes = {MpIntroductionApplication.class}) class TaskServiceImplTest {
@Autowired
private TaskService taskService;
@Test
public void testWrapperWhen2Gt() {
QueryWrapper<Task> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name");
queryWrapper.orderByAsc("id");
queryWrapper.last("limit 10"); for (int i = 0; i < 2; i++) {
queryWrapper.gt("id", 10);
}
List<Task> taskList = taskService.list(queryWrapper);
log.info("{} ", taskList);
}
}执行结果:
2022-08-16 10:04:29.535 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList : ==> Preparing: SELECT id,name FROM task WHERE deleted=false AND (id > ? AND id > ?) ORDER BY id ASC limit 10
2022-08-16 10:04:29.560 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList : ==> Parameters: 10(Integer), 10(Integer)
2022-08-16 10:04:29.584 DEBUG 57006 --- [main] c.d.m.a.r.t.m.TaskMapper.selectList : <== Total: 10复现了。MyBatis-Plus的Wrapper生成sql时,是一种append操作。
使用条件构造器Wrapper时,单独构建每次用到的SQL。
public class BizDataService { public void doTask() {
log.info("任务开始");
QueryWrapper<BizData> queryWrapper = new QueryWrapper<>();
queryWrapper.in("status", 1, 2, 3);
queryWrapper.isNotNull("biz_filed_1");
queryWrapper.select("id", "biz_filed_1");
queryWrapper.orderByAsc("id");
queryWrapper.last("limit 10 ");
List<BizData> bizDataList = bizDataService.list(queryWrapper); if (CollectionUtils.isEmpty(bizDataList)) {
log.info("没有满足条件的数据"); return;
} while (true) {
updateData(traceId, bizDataList); if (CollectionUtils.isEmpty(bizDataList)) {
log.info("任务 完成 "); return;
}
Long lastId = bizDataList.get(bizDataList.size() - 1).getId();
log.info(" lastId {} ", lastId); /**
* 这部分有重复,可以额外抽一个buildQueryWrapper的方法来封装这个重复
*/
queryWrapper = new QueryWrapper<>();
queryWrapper.in("status", 1, 2, 3);
queryWrapper.isNotNull("biz_filed_1");
queryWrapper.select("id", "biz_filed_1");
queryWrapper.orderByAsc("id");
queryWrapper.last("limit 10 ");
queryWrapper.gt("id", lastId);
bizDataList = bizDataService.list(queryWrapper);
}
}
}使用新的API或组件时,要有重点地进行测试。
语法糖虽好,用不好会粘牙哦
另,思路比结论重要
展开聊一下。
构建SQL的功能是由类com.baomidou.mybatisplus.core.conditions.AbstractWrapper承担的。来重点看这个类:

AbstractWrapper实际上实现了五大接口:
嵌套接口Nested、
比较接口Compare、
拼接接口Join、
函数接口Func、
SQL片断函数接口ISqlSegment。

Wrapper的gt由比较接口Compare和SQL片断函数接口ISqlSegment来承接。
public interface Compare<Children, R> extends Serializable {
default Children gt(R column, Object val) {
return this.gt(true, column, val);
}
Children gt(boolean condition, R column, Object val);
}public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {
public Children gt(boolean condition, R column, Object val) {
/**
* 条件构造器的处理策略是append
*/
return this.addCondition(condition, column, SqlKeyword.GT, val);
}
protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
return this.maybeDo(condition, () -> {
/**
* 所有的拼sql指令,都是由appendSql来承接
*/
this.appendSqlSegments(this.columnToSqlSegment(column), sqlKeyword, () -> {
return this.formatParam((String) null, val);
});
});
}
/**
* 所有append就是把所有的条件add到一个List中
* @param sqlSegments
*/
protected void appendSqlSegments(ISqlSegment... sqlSegments) {
this.expression.add(sqlSegments);
}
}gt gt(R column, Object val) gt(boolean condition, R column, Object val) 大于 > 例: gt("age", 18)--->age > 18 https://baomidou.com/pages/10c804/#ne
MyBatis-Plus唯一进行过去重的是last方法: last
last(String lastSql)
last(boolean condition,String lastSql)无视优化规则直接拼接到 sql 的最后
注意事项: 只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用 例: last("limit 1")
last方法的实现:覆盖上一次
public interface Join<Children> extends Serializable {
default Children last(String lastSql) {
return this.last(true, lastSql);
}
Children last(boolean condition, String lastSql);
}public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> { protected final Children typedThis = this;
protected SharedString lastSql;
public Children last(boolean condition, String lastSql) {
if (condition) {
/**
* 条件构造器的处理策略是覆盖上次的值
*/
this.lastSql.setStringValue(" " + lastSql);
}
return this.typedThis;
}
}只要一个还有追求,他就没有老。直到后悔取代了梦想,一个人才算老。