欢迎阅读我的文章!更多精彩内容,欢迎关注:
• B站主页
小枫Geek
• 微信公众号Procode  


在 Spring Boot 项目中,MyBatis-Plus(简称 MP)几乎是最常见的 ORM 框架之一。 它不仅简化了 CRUD,还内置了分页插件、批量操作、逻辑删除、乐观锁等功能,大幅降低了开发成本。

不过在实际开发中,很多同学还是会遇到一些常见问题:

  • 分页插件分页不生效、总数统计太慢;

  • 批量更新写起来麻烦,性能不佳;

  • 大表查询性能差,优化无从下手。

本文就来结合实际场景,系统梳理 Spring Boot + MyBatis-Plus 的几个高频问题和解决方案。


1、分页问题

1. 分页插件没生效

常见现象:

  • 代码里写了分页,但结果还是全量返回;

  • Page 对象总是空,total 为 0;

原因分析:

  • MyBatis-Plus 3.4 之后需要显式注册分页插件;

  • 旧版 PaginationInterceptor 已废弃,应该用 MybatisPlusInterceptor

正确写法:

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

分页查询示例:

Page<User> page = new Page<>(1, 10); // 第1页, 每页10条
Page<User> result = userMapper.selectPage(page, new QueryWrapper<User>().eq("status", 1));

System.out.println("总数:" + result.getTotal());
System.out.println("数据:" + result.getRecords());

2. 分页 count 查询太慢

当表数据量过千万时,分页插件默认会执行:

SELECT COUNT(*) FROM user WHERE status = 1;

大表统计会拖垮性能。

优化方案:

  • 手动控制 count 查询

Page<User> page = new Page<>(1, 10, false); // 不查总数
  • 条件优化:建合适索引,让 count 更快。

  • 缓存总数:对于变化不频繁的表,可以用 Redis 缓存 count 值。

  • 搜索类分页:如果只是“加载更多”,可以用游标分页(基于 ID)。

2、批量更新问题

1. 常见写法性能低

很多人用 for 循环一个个 updateById,导致 SQL 执行多次:

for(User user : userList) {
    userMapper.updateById(user);
}

这种写法对数据库压力大,效率低。


2. 推荐的批量更新方式

方式一:MP 提供的 updateBatchByIdService 层已经内置了批量更新方法:

boolean flag = userService.updateBatchById(userList, 1000);

第二个参数 1000 表示分批大小,避免 SQL 太长。


方式二:自定义 SQL + foreach如果需要根据条件批量更新,可以在 Mapper 里写 XML:

<update id="batchUpdateStatus">
    update user 
    set status = #{status}
    where id in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

调用:

userMapper.batchUpdateStatus(1, Arrays.asList(1,2,3,4));

这种方式能一次性更新多条数据,效率远高于单条 update。

3、性能优化问题

1. 大表分页优化

  • 问题:limit offset 分页在 offset 很大时性能极差。

  • 优化方案: 改用 ID 游标分页

SELECT * FROM user WHERE id > #{lastId} ORDER BY id ASC LIMIT 10;

比传统的 limit 100000, 10 快得多。


2. 查询列过多

  • 问题:select * 会返回所有字段,带宽和内存浪费。

  • 优化方案: 只查必要字段。

QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "username", "email").eq("status", 1);

List<User> users = userMapper.selectList(wrapper);

3. N+1 查询问题

  • 问题: 查询用户列表后,再为每个用户查订单,导致大量 SQL。

  • 优化方案: 用一次性 in 查询 + 关联组装。

List<Order> orders = orderMapper.selectList(
    new QueryWrapper<Order>().in("user_id", userIds)
);

然后在 Java 内存里做关联,避免重复 SQL。


4. 批量插入优化

MP 的 saveBatch 默认也是分批 insert,提高效率:

userService.saveBatch(userList, 1000);

如果数据量极大,可以考虑 MyBatis-Plus 扩展库 mybatis-plus-join 或者直接用 INSERT INTO ... VALUES ... 多值插入。

4、总结

  1. 分页

    • 确保注册分页插件;

    • 大表分页尽量用游标分页;

    • count 查询可缓存或优化索引。

  2. 批量更新

    • 避免 for 循环逐条更新;

    • 优先用 updateBatchById 或自定义 foreach。

  3. 性能优化

    • 少用 select *,明确字段;

    • 合理设计索引;

    • 避免 N+1 查询,多用 in 查询批量获取。

MyBatis-Plus 已经解决了大部分通用问题,但在实际业务中:

  • 如果你遇到复杂多表查询,可能需要自己写 SQL,而不是依赖 MP 提供的 CRUD;

  • 在超大规模数据(亿级别)场景下,分页和批量操作还需要结合 分库分表读写分离 来优化;

  • 在微服务架构中,建议结合分页 API 标准化,避免不同团队使用不同分页参数。

Logo

更多推荐