1. 当批量操作遇上传统操作:性能差在哪?

每次去超市买3件商品非要分3次结账,这样的行为大家都会觉得滑稽吧?但在数据库操作中,很多新手确实在用类似的方式和数据库"打交道"。常规的单条操作就像零散购物,批量操作则像批量采购,两者的效率差距可以到百倍量级。

传统逐条操作的致命缺陷:

  • 网络传输的隐性成本:每次请求的协议头/认证信息会吃掉60%的通信开销
  • SQL解析的重复劳动:每条语句都要经历词法分析→语法解析→执行计划生成
  • 事务管理的无形损耗:隐式事务提交会产生1000次commit可能

示例场景直观对比:

// 常规方式:耗时约5秒(1000条记录)
for (User user : userList) {
    userMapper.insert(user); 
}

// 批量方式:耗时仅0.3秒 
userMapper.batchInsert(userList);

2. 批量插入

2.1 基于BatchExecutor的批量发射

这是MyBatis原生的批量模式,像极了装填多枚导弹的发射装置。

配置示例:

<insert id="batchInsert" parameterType="list">
    INSERT INTO t_user(name,age) 
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name},#{item.age})
    </foreach>
</insert>

执行秘籍:

// 获取批处理模式的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
        mapper.insert(user);
    }
    sqlSession.commit(); // 统一提交
} finally {
    sqlSession.close();
}

注意事项:

  • 数据规模超过3000条时需要分批次处理
  • 配合rewriteBatchedStatements=true参数才能激活MySQL真批量
  • 内存消耗与批次数成正比,建议做好JVM监控

3. 批量更新的精巧设计

3.1 动态SQL的妙用:一石多鸟式更新

类似于用万能扳手调整不同尺寸的螺丝,实现智能匹配。

XML映射示例:

<update id="batchUpdate">
    UPDATE t_product
    <trim prefix="SET" suffixOverrides=",">
        <trim prefix="price = CASE" suffix="END,">
            <foreach collection="list" item="item">
                WHEN id = #{item.id} THEN #{item.price}
            </foreach>
        </trim>
        update_time = NOW()
    </trim>
    WHERE id IN 
    <foreach collection="list" item="item" open="(" separator="," close=")">
        #{item.id}
    </foreach>
</update>

这种方法相当于把多个UPDATE合并成一个,避免了多次索引查找。但在主从架构中要注意binlog同步模式。


4. 批量删除的思维突破

4.1 IN语句的效率陷阱与解决之道

直接使用IN语句就像用大网捞鱼,稍有不慎就会翻船。

优化方案对比:

/* 常规方式 */
DELETE FROM t_log WHERE id IN (1,2,3...10000);

/* 分页批处理 */
DELETE FROM t_log WHERE id BETWEEN 1 AND 1000;
DELETE FROM t_log WHERE id BETWEEN 1001 AND 2000;

为什么分页更好?

  • 减少锁范围:缩小单次操作的影响范围
  • 避免超时报错:大事务容易导致锁等待超时
  • 保证可中断性:批量失败时可分段重试

5. 避坑指南:那些年我遇到的批量BUG

5.1 内存溢出血泪史

在32位JVM上执行百万级批量插入,系统直接崩溃。解决方案:

  • 使用流式处理分段提交
  • 添加内存占用监控模块
  • 调整JVM参数:-XX:+UseParallelOldGC

5.2 主键冲突的幽灵事件

批量操作中主键生成策略要特别注意:

<!-- 错误示例:批量插入共用同一个SELECT KEY -->
<selectKey resultType="long" keyProperty="id" order="BEFORE">
    SELECT NEXT VALUE FOR MY_SEQ
</selectKey>

<!-- 正确做法:使用数据库自增 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">

6. 性能优化进阶技巧

6.1 控制流技术的巧妙结合

像交通指挥中心调控车流一样管理批量操作:

参数优化示例:

rewriteBatchedStatements=true
allowMultiQueries=true
useServerPrepStmts=false

# MyBatis配置
executorType=batch
defaultStatementTimeout=300

7. 应用场景分析

7.1 适合场景

  • 电商大促期间的订单批量创建
  • 物联设备数据的高频上报存储
  • 金融行业的日终批量结息

7.2 慎用场景

  • ACL权限控制严格的操作
  • 需要严格保证顺序的数据操作
  • 高并发下的热点数据更新

8. 技术选型对比

方式 优点 缺点
foreach标签 开发简单 参数长度受限
BatchExecutor 性能最高 需要手动管理Session
存储过程 数据库端处理 移植性差
多值插入 语法简洁 数据量大时报错困难

9. 总结与展望

通过合理运用MyBatis的批量操作技巧,我们成功将某金融系统日终处理的耗时从3小时压缩到18分钟。就像给数据库操作装上了涡轮增压器,关键在于:

  1. 理解不同批量方式的适配场景
  2. 掌握性能优化的参数调优方法
  3. 建立完善的异常处理机制

未来可探索的方向:

  • 与Redis管道技术的结合应用
  • 响应式编程模式下的批量优化
  • 基于机器学习的数据分片策略