选择持久层框架时,开发者常对自动生成SQL的性能存疑。本文通过实测对比MyBatis系列框架,揭示自动SQL生成的真实性能表现。

这些担忧合理,但事实如此吗?
我对主流的 MyBatis 系列框架做了一次性能测试:
用真实的测试数据回答:自动生成 SQL 的性能代价到底有多大?
-Xms1g
-Xmx4g
CREATE TABLE `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50),
`age` INT,
`status` TINYINT,
`email` VARCHAR(100),
`phone` VARCHAR(20),
`create_time` DATETIME,
`update_time` DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
8个字段,贴近真实业务。只有主键索引,避免索引优化干扰。
插入操作:
更新操作:
查询操作:
JVM 在首次执行时会进行类加载、JIT 编译等初始化工作。每个测试项连续执行 15 轮,第 1 轮单独记录,第 2-15 轮用于统计。
统计指标:
四个框架执行完全相同的业务逻辑。
<insert id="insert">
INSERT INTO user (id, username, age, status, email, phone, create_time, update_time)
VALUES (#{id}, #{username}, #{age}, #{status}, #{email}, #{phone}, #{createTime}, #{updateTime})
insert><select id="findByIdAndAgeAndStatus" resultType="User">
SELECT * FROM user
WHERE id = #{id} AND age = #{age} AND status = #{status}
select><select id="findDynamicByConditions" resultType="User">
SELECT * FROM user
<where>
<if test="id != null">AND id = #{id}if>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
if>
<if test="age != null">AND age > #{age}if>
<if test="statusList != null and statusList.size() > 0">
AND status IN
<foreach item="item" collection="statusList" open="(" separator="," close=")">
#{item}
foreach>
if>
where>
select>
public interface UserDao extends SimpleDao {
// 继承自 SimpleDao 的方法:
// int insert(User user);
// int insertBatch(List users, int batchSize);
// int updateById(User user);
// int updateBatchById(List users, int batchSize);
// User findById(Long id);
// 方法名生成查询 SQL
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status);
// 复杂条件查询
List findByIdAndUsernameLikeAndAgeGtAndStatusIn(
Long id, String username, Integer age, List statusList
);
// 动态查询(使用 QueryEntity)
@Dynamic
List findDynamicByIdAndUsernameLikeAndAgeGtAndStatusIn(UserQuery query);
}
public interface UserMapper extends BaseMapper {
}// Service 层构建查询
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status) {
return userMapper.selectList(
new LambdaQueryWrapper()
.eq(User::getId, id)
.eq(User::getAge, age)
.eq(User::getStatus, status)
);
}// 动态查询
List findDynamicByConditions(Long id, String username, Integer age, List statusList) {
return userMapper.selectList(
new LambdaQueryWrapper()
.eq(id != null, User::getId, id)
.like(StringUtils.isNotBlank(username), User::getUsername, username)
.gt(age != null, User::getAge, age)
.in(CollectionUtils.isNotEmpty(statusList), User::getStatus, statusList)
);
}
public interface UserMapper extends BaseMapper {
}// Service 层构建查询
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status) {
return userMapper.selectListByQuery(
QueryWrapper.create()
.eq(User::getId, id)
.eq(User::getAge, age)
.eq(User::getStatus, status)
);
}// 动态查询
List findDynamicByConditions(Long id, String username, Integer age, List statusList) {
QueryWrapper query = QueryWrapper.create();
if (id != null) query.eq(User::getId, id);
if (StringUtils.isNotBlank(username)) query.like(User::getUsername, username);
if (age != null) query.gt(User::getAge, age);
if (CollectionUtils.isNotEmpty(statusList)) query.in(User::getStatus, statusList);
return userMapper.selectListByQuery(query);
}
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 57,594 μs | 1,830 μs | 1,544 μs | 2,262 μs |
| MyBatisGX | 79,329 μs | 1,922 μs | 1,586 μs | 2,632 μs |
| MyBatis-Flex | 314,609 μs | 2,587 μs | 2,217 μs | 3,145 μs |
| MyBatis-Plus | 118,034 μs | 2,234 μs | 1,876 μs | 2,848 μs |
首次执行,MyBatis-Flex 耗时最长(314ms),MyBatis 最短(57ms)。热身后,四者都在 1.82.6ms 之间,最大差距 757μs。各框架波动范围在 400900μs。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 41 ms | 11 ms | 6 ms | 25 ms |
| MyBatisGX | 36 ms | 12 ms | 7 ms | 56 ms |
| MyBatis-Flex | 53 ms | 21 ms | 10 ms | 40 ms |
| MyBatis-Plus | 80 ms | 20 ms | 10 ms | 117 ms |
热身后,MyBatis 和 MyBatisGX 在 1012ms,MyBatis-Flex 和 MyBatis-Plus 在 2021ms。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 459 ms | 307 ms | 239 ms | 653 ms |
| MyBatisGX | 742 ms | 323 ms | 264 ms | 569 ms |
| MyBatis-Flex | 1,025 ms | 381 ms | 293 ms | 581 ms |
| MyBatis-Plus | 690 ms | 446 ms | 350 ms | 686 ms |
热身后:MyBatis 307ms < MyBatisGX 323ms < MyBatis-Flex 381ms < MyBatis-Plus 446ms。MyBatisGX 比 MyBatis 慢 16ms(约 5%),MyBatis-Plus 慢 139ms(约 45%)。所有框架都在同一数量级。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 3,302 μs | 1,139 μs | 877 μs | 1,318 μs |
| MyBatisGX | 1,987 μs | 1,323 μs | 1,035 μs | 2,523 μs |
| MyBatis-Flex | 4,109 μs | 1,308 μs | 863 μs | 1,772 μs |
| MyBatis-Plus | 15,781 μs | 1,558 μs | 1,230 μs | 2,070 μs |
热身后,四者都在 1.1~1.6ms 之间,最大差距 419μs。MyBatis-Plus 首次执行明显慢于其他框架。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 21,109 μs | 1,350 μs | 1,123 μs | 1,524 μs |
| MyBatisGX | 24,655 μs | 1,574 μs | 1,244 μs | 2,182 μs |
| MyBatis-Flex | 58,886 μs | 1,362 μs | 1,054 μs | 1,890 μs |
| MyBatis-Plus | 29,328 μs | 1,950 μs | 1,591 μs | 2,679 μs |
热身后,四者都在 1.3~2.0ms 之间,最大差距 600μs。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 46 ms | 17 ms | 12 ms | 45 ms |
| MyBatisGX | 112 ms | 21 ms | 16 ms | 30 ms |
| MyBatis-Flex | 105 ms | 25 ms | 18 ms | 65 ms |
| MyBatis-Plus | 107 ms | 26 ms | 19 ms | 46 ms |
热身后,四者都在 17~26ms 之间。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 1,427 ms | 1,458 ms | 1,294 ms | 1,618 ms |
| MyBatisGX | 1,588 ms | 1,635 ms | 1,423 ms | 1,855 ms |
| MyBatis-Flex | 1,872 ms | 1,568 ms | 1,397 ms | 1,872 ms |
| MyBatis-Plus | 1,948 ms | 1,725 ms | 1,497 ms | 1,965 ms |
热身后,四者都在 1.4~1.7 秒,最大差距 267ms。MyBatisGX 比 MyBatis 慢 177ms(约 12%)。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 17,080 μs | 1,314 μs | 889 μs | 1,813 μs |
| MyBatisGX | 14,819 μs | 1,472 μs | 1,047 μs | 2,051 μs |
| MyBatis-Flex | 26,498 μs | 1,232 μs | 777 μs | 1,512 μs |
| MyBatis-Plus | 25,419 μs | 1,333 μs | 1,026 μs | 2,101 μs |
热身后,四者都在 1.21.5ms 之间,最大差距 240μs。各框架波动范围在 7001000μs。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 2,645 μs | 1,198 μs | 952 μs | 1,789 μs |
| MyBatisGX | 2,467 μs | 1,451 μs | 952 μs | 1,879 μs |
| MyBatis-Flex | 4,449 μs | 1,499 μs | 1,163 μs | 1,955 μs |
| MyBatis-Plus | 9,865 μs | 2,059 μs | 1,673 μs | 3,098 μs |
热身后,MyBatis-Plus 慢于其他三个框架。MyBatis、MyBatisGX、MyBatis-Flex 三者在 1.2~1.5ms 之间。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 4,177 μs | 1,536 μs | 1,148 μs | 2,134 μs |
| MyBatisGX | 11,444 μs | 1,512 μs | 1,098 μs | 1,886 μs |
| MyBatis-Flex | 2,474 μs | 1,116 μs | 919 μs | 1,227 μs |
| MyBatis-Plus | 6,863 μs | 1,687 μs | 1,352 μs | 2,187 μs |
热身后,四者都在 1.1~1.7ms 之间,最大差距 571μs。MyBatis-Flex 表现较好(1,116μs)。
| 框架 | 首次执行 | 热身后平均 | 最快 | 最慢 |
|---|---|---|---|---|
| MyBatis | 14,796 μs | 1,897 μs | 1,529 μs | 2,316 μs |
| MyBatisGX | 2,193 μs | 1,476 μs | 1,145 μs | 1,804 μs |
| MyBatis-Flex | 1,723 μs | 1,386 μs | 1,058 μs | 1,777 μs |
| MyBatis-Plus | 2,696 μs | 1,584 μs | 1,242 μs | 2,163 μs |
这是最接近真实业务的测试。条件参数可能为空,需要动态构建 WHERE 子句。
首次执行,MyBatis 耗时 14,796μs,显著高于其他三个框架(1,723μs ~ 2,696μs)。热身后,四个框架都在 1.4~1.9ms 之间,最大差距 511μs。
波动范围:
多轮测试结果可重复。热身后四个框架的性能在同一数量级。
既然性能都差不多,为什么选 MyBatisGX?
在不牺牲性能的前提下,提升开发效率和代码可维护性。
MyBatis(手写 XML):
<select id="findByIdAndAgeAndStatus" resultType="User">
SELECT id, username, age, status, email, phone, create_time, update_time
FROM user
WHERE id = #{id} AND age = #{age} AND status = #{status}
select>
// UserMapper.java
List findByIdAndAgeAndStatus(
@Param("id") Long id,
@Param("age") Integer age,
@Param("status") Integer status
);
MyBatisGX(方法名生成):
// UserDao.java - 无需 XML
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status);
MyBatis-Plus(Wrapper):
// Service 层
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status) {
return userMapper.selectList(
new LambdaQueryWrapper()
.eq(User::getId, id)
.eq(User::getAge, age)
.eq(User::getStatus, status)
);
}
MyBatis-Flex(QueryWrapper):
// Service 层
List findByIdAndAgeAndStatus(Long id, Integer age, Integer status) {
return userMapper.selectListByQuery(
QueryWrapper.create()
.eq(User::getId, id)
.eq(User::getAge, age)
.eq(User::getStatus, status)
);
}
代码量对比: