一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

自动生成SQL是否影响性能:对比测试MyBatisGX与MyBatis系列框架MyBatis-Plus和MyBatis-Flex

时间:2026-05-29 19:15:01 编辑:袖梨 来源:一聚教程网

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

自动生成 SQL 会拖慢性能吗?实测 MyBatisGX、MyBatis、MyBatis-Plus、MyBatis-Flex

一、引言

自动生成 SQL 会拖慢性能吗?实测 MyBatisGX、MyBatis、MyBatis-Plus、MyBatis-Flex

  1. "自动生成的 SQL 肯定不如手写的快"
  2. "ORM 框架抽象层越高,性能损耗越大"
  3. "为了开发效率牺牲性能,划算吗?"

这些担忧合理,但事实如此吗?

我对主流的 MyBatis 系列框架做了一次性能测试:

  1. MyBatis:原生手写 SQL,性能基准
  2. MyBatisGX:方法名生成 SQL + 预生成机制
  3. MyBatis-Plus:运行时动态生成 SQL
  4. MyBatis-Flex:轻量级动态生成

用真实的测试数据回答:自动生成 SQL 的性能代价到底有多大?


二、测试环境

硬件配置

  1. CPU:4核8线程
  2. 内存:16GB

软件配置

  1. JDK 版本:21
  2. MySQL 版本:5.7
  3. Spring Boot 版本:3.x

JVM 参数

-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个字段,贴近真实业务。只有主键索引,避免索引优化干扰。


三、测试方法

3.1 测试项目

插入操作:

  1. 单条插入
  2. 批量插入 100 条
  3. 批量插入 10,000 条

更新操作:

  1. 单条更新
  2. 动态更新(仅更新非空字段)
  3. 批量更新 100 条
  4. 批量更新 10,000 条

查询操作:

  1. 主键查询
  2. 简单条件查询(3个条件)
  3. 复杂条件查询(Like、In、Gt 等)
  4. 动态条件查询

3.2 测试策略

JVM 在首次执行时会进行类加载、JIT 编译等初始化工作。每个测试项连续执行 15 轮,第 1 轮单独记录,第 2-15 轮用于统计。

统计指标:

  1. 首次执行耗时
  2. 热身后平均值
  3. 热身后最快值
  4. 热身后最慢值

3.3 四个框架的实现方式

四个框架执行完全相同的业务逻辑。

MyBatis(手写 XML)
<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>
MyBatisGX(方法名生成)
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);
}
MyBatis-Plus(Wrapper)
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)
    );
}
MyBatis-Flex(QueryWrapper)
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);
}

四、测试结果

4.1 单条插入

框架首次执行热身后平均最快最慢
MyBatis57,594 μs1,830 μs1,544 μs2,262 μs
MyBatisGX79,329 μs1,922 μs1,586 μs2,632 μs
MyBatis-Flex314,609 μs2,587 μs2,217 μs3,145 μs
MyBatis-Plus118,034 μs2,234 μs1,876 μs2,848 μs

首次执行,MyBatis-Flex 耗时最长(314ms),MyBatis 最短(57ms)。热身后,四者都在 1.82.6ms 之间,最大差距 757μs。各框架波动范围在 400900μs。


4.2 批量插入 100 条

框架首次执行热身后平均最快最慢
MyBatis41 ms11 ms6 ms25 ms
MyBatisGX36 ms12 ms7 ms56 ms
MyBatis-Flex53 ms21 ms10 ms40 ms
MyBatis-Plus80 ms20 ms10 ms117 ms

热身后,MyBatis 和 MyBatisGX 在 1012ms,MyBatis-Flex 和 MyBatis-Plus 在 2021ms。


4.3 批量插入 10,000 条

框架首次执行热身后平均最快最慢
MyBatis459 ms307 ms239 ms653 ms
MyBatisGX742 ms323 ms264 ms569 ms
MyBatis-Flex1,025 ms381 ms293 ms581 ms
MyBatis-Plus690 ms446 ms350 ms686 ms

热身后:MyBatis 307ms < MyBatisGX 323ms < MyBatis-Flex 381ms < MyBatis-Plus 446ms。MyBatisGX 比 MyBatis 慢 16ms(约 5%),MyBatis-Plus 慢 139ms(约 45%)。所有框架都在同一数量级。


4.4 单条更新

框架首次执行热身后平均最快最慢
MyBatis3,302 μs1,139 μs877 μs1,318 μs
MyBatisGX1,987 μs1,323 μs1,035 μs2,523 μs
MyBatis-Flex4,109 μs1,308 μs863 μs1,772 μs
MyBatis-Plus15,781 μs1,558 μs1,230 μs2,070 μs

热身后,四者都在 1.1~1.6ms 之间,最大差距 419μs。MyBatis-Plus 首次执行明显慢于其他框架。


4.5 动态更新(UpdateSelective)

框架首次执行热身后平均最快最慢
MyBatis21,109 μs1,350 μs1,123 μs1,524 μs
MyBatisGX24,655 μs1,574 μs1,244 μs2,182 μs
MyBatis-Flex58,886 μs1,362 μs1,054 μs1,890 μs
MyBatis-Plus29,328 μs1,950 μs1,591 μs2,679 μs

热身后,四者都在 1.3~2.0ms 之间,最大差距 600μs。


4.6 批量更新 100 条

框架首次执行热身后平均最快最慢
MyBatis46 ms17 ms12 ms45 ms
MyBatisGX112 ms21 ms16 ms30 ms
MyBatis-Flex105 ms25 ms18 ms65 ms
MyBatis-Plus107 ms26 ms19 ms46 ms

热身后,四者都在 17~26ms 之间。


4.7 批量更新 10,000 条

框架首次执行热身后平均最快最慢
MyBatis1,427 ms1,458 ms1,294 ms1,618 ms
MyBatisGX1,588 ms1,635 ms1,423 ms1,855 ms
MyBatis-Flex1,872 ms1,568 ms1,397 ms1,872 ms
MyBatis-Plus1,948 ms1,725 ms1,497 ms1,965 ms

热身后,四者都在 1.4~1.7 秒,最大差距 267ms。MyBatisGX 比 MyBatis 慢 177ms(约 12%)。


4.8 主键查询(FindById)

框架首次执行热身后平均最快最慢
MyBatis17,080 μs1,314 μs889 μs1,813 μs
MyBatisGX14,819 μs1,472 μs1,047 μs2,051 μs
MyBatis-Flex26,498 μs1,232 μs777 μs1,512 μs
MyBatis-Plus25,419 μs1,333 μs1,026 μs2,101 μs

热身后,四者都在 1.21.5ms 之间,最大差距 240μs。各框架波动范围在 7001000μs。


4.9 简单条件查询(FindByIdAndAgeAndStatus)

框架首次执行热身后平均最快最慢
MyBatis2,645 μs1,198 μs952 μs1,789 μs
MyBatisGX2,467 μs1,451 μs952 μs1,879 μs
MyBatis-Flex4,449 μs1,499 μs1,163 μs1,955 μs
MyBatis-Plus9,865 μs2,059 μs1,673 μs3,098 μs

热身后,MyBatis-Plus 慢于其他三个框架。MyBatis、MyBatisGX、MyBatis-Flex 三者在 1.2~1.5ms 之间。


4.10 复杂条件查询(Like + In + Gt)

框架首次执行热身后平均最快最慢
MyBatis4,177 μs1,536 μs1,148 μs2,134 μs
MyBatisGX11,444 μs1,512 μs1,098 μs1,886 μs
MyBatis-Flex2,474 μs1,116 μs919 μs1,227 μs
MyBatis-Plus6,863 μs1,687 μs1,352 μs2,187 μs

热身后,四者都在 1.1~1.7ms 之间,最大差距 571μs。MyBatis-Flex 表现较好(1,116μs)。


4.11 动态条件查询

框架首次执行热身后平均最快最慢
MyBatis14,796 μs1,897 μs1,529 μs2,316 μs
MyBatisGX2,193 μs1,476 μs1,145 μs1,804 μs
MyBatis-Flex1,723 μs1,386 μs1,058 μs1,777 μs
MyBatis-Plus2,696 μs1,584 μs1,242 μs2,163 μs

这是最接近真实业务的测试。条件参数可能为空,需要动态构建 WHERE 子句。

首次执行,MyBatis 耗时 14,796μs,显著高于其他三个框架(1,723μs ~ 2,696μs)。热身后,四个框架都在 1.4~1.9ms 之间,最大差距 511μs。

波动范围:

  1. MyBatis:787μs(1,529 ~ 2,316)
  2. MyBatisGX:659μs(1,145 ~ 1,804)
  3. MyBatis-Flex:719μs(1,058 ~ 1,777)
  4. MyBatis-Plus:921μs(1,242 ~ 2,163)

多轮测试结果可重复。热身后四个框架的性能在同一数量级。


五、MyBatisGX 的价值定位

既然性能都差不多,为什么选 MyBatisGX?

在不牺牲性能的前提下,提升开发效率和代码可维护性。

5.1 代码对比:不同框架的实现方式

场景1:简单查询

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)
    );
}

代码量对比:

  1. MyBatis:XML + 接口定义,约 8 行

热门栏目