1. 引言
1.1 MyBatis Plus 简介与优势
[MyBatis Plus]是基于 MyBatis 的增强工具,简化了数据库操作、提高了开发效率,广泛用于 Spring Boot 项目中。
主要特性包括:
- 无侵入:只做增强不做改变;
- 支持 Lambda 表达式构建查询条件;
- 内置通用 CRUD 操作;
- 分页插件支持;
- 多租户插件;
- 自动填充功能;
- 性能分析插件等。
示例:简单的增删改查
// 定义实体类
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
// Mapper 接口
public interface UserMapper extends BaseMapper<User> {
}
// 使用示例
@Autowired
private UserMapper userMapper;
public void testSelect() {
List<User> users = userMapper.selectList(null); // 查询所有用户
users.forEach(System.out::println);
}
1.2 大数据量场景下的常见性能问题
当数据量达到 十万级、百万级甚至千万级 时,以下问题会逐渐暴露出来:
- 查询响应时间变长(如分页查询);
- 占用大量内存资源;
- 数据库连接池耗尽;
- CPU 和 IO 压力增大;
- ORM 映射效率低下;
- 不合理的 SQL 语句导致全表扫描。
例如,下面是一个典型的低效分页查询:
// 错误写法:使用 offset 分页在大数据量下效率极低
Page<User> page = new Page<>(10000, 10); // 第 10000 页,每页 10 条
userMapper.selectPage(page, null);
上述代码在 MySQL 中生成的 SQL 类似于:
SELECT * FROM user LIMIT 100000, 10;
这会导致 MySQL 必须扫描前 100000 条记录后才返回结果,效率非常低。
2. MyBatis Plus 查询慢的常见原因分析
2.1 SQL 语句未优化(全表扫描)
如果查询没有走索引,MySQL 就会进行全表扫描,随着数据量增长,性能急剧下降。
示例:
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "Tom");
List<User> users = userMapper.selectList(wrapper);
如果 name
字段没有索引,MySQL 只能进行全表扫描。
解决方案:
为常用查询字段添加索引:
ALTER TABLE user ADD INDEX idx_name (name);
2.2 分页查询效率低下(offset 性能问题)
如前所述,使用 LIMIT offset, size
的方式在偏移量较大时性能极差。
优化建议:
- 使用游标分页(Cursor-based Pagination);
- 使用 ID 范围分页;
- 使用 Redis 缓存高频访问页的数据。
游标分页示例:
Long lastId = 0L; // 上一页最后一个ID
int pageSize = 10;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("id", lastId)
.orderByAsc("id")
.last("LIMIT " + pageSize);
List<User> users = userMapper.selectList(wrapper);
2.3 数据库索引使用不当
索引不是越多越好,也不是所有查询都能命中索引。
常见误区:
- 对低基数字段建索引(如性别字段);
- 没有使用复合索引的最左匹配原则;
- 使用函数或表达式导致索引失效。
示例:索引失效
wrapper.like("name", "Tom"); // LIKE '%Tom%' 不走索引
应尽量避免前缀模糊查询,或者使用全文索引替代。
2.4 ORM 映射带来的额外开销
ORM 框架虽然简化了开发,但也带来了对象创建、属性赋值等额外开销。
优化建议:
- 使用 Map 或 DTO 投影减少映射负担;
- 避免自动映射不必要的字段;
- 使用
selectObjs()
获取简单数据。
示例:使用 Map 查询
List<Map<String, Object>> maps = userMapper.selectMaps(new QueryWrapper<User>().eq("age", 25));
maps.forEach(System.out::println);
输出:
{
name=Tom, age