MyBatis的性能优化——批量操作

批量操作是数据处理场景中的性能关键点,尤其在数据迁移、报表生成等大数据量场景下,优化效果可提升10倍以上性能。以下从7个维度系统解析批量操作优化策略:

一、基础批量插入优化

1.1 常规foreach方式

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

优化要点:

数据分块:单次插入控制在500-1000条(避免SQL过长)
批大小控制:

// 分批次提交
int batchSize = 500;
for (int i = 0; i < total; i += batchSize) {
    List<User> subList = list.subList(i, Math.min(i + batchSize, total));
    mapper.batchInsert(subList);
}

1.2 批处理模式(BATCH Executor)

try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User user : userList) {
        mapper.insert(user);
        if (counter++ % 500 == 0) {
            session.flushStatements(); // 分段提交
        }
    }
    session.commit(); // 最终提交
}

优势对比:

方式10,000条耗时内存占用事务控制
foreach12s单事务
BATCH模式3.2s可分段控制

二、批量更新优化策略

2.1 CASE WHEN批量更新

UPDATE users SET 
    name = CASE id
        WHEN 1 THEN '张三'
        WHEN 2 THEN '李四'
    END,
    age = CASE id
        WHEN 1 THEN 25
        WHEN 2 THEN 30
    END
WHERE id IN (1,2)

MyBatis动态实现:

<update id="batchUpdate">
    UPDATE users
    <set>
        <foreach collection="list" item="user" index="index">
            <if test="user.name != null">
                name = CASE id
                    WHEN #{user.id} THEN #{user.name}
                END,
            </if>
            age = CASE id
                WHEN #{user.id} THEN #{user.age}
            END
        </foreach>
    </set>
    WHERE id IN 
    <foreach collection="list" item="user" open="(" separator="," close=")">
        #{user.id}
    </foreach>
</update>

2.2 分批次并行更新

// 使用并行流处理(注意线程安全)
List<List<User>> partitions = Lists.partition(userList, 500);
partitions.parallelStream().forEach(subList -> {
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        subList.forEach(mapper::update);
        session.commit();
    }
});

三、批量删除优化方案

3.1 使用IN语句

<delete id="batchDelete">
    DELETE FROM users WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

注意:
IN条件数量限制:MySQL默认max_allowed_packet=4MB
分页删除避免锁表:

DELETE FROM users WHERE id IN (...)
LIMIT 1000; -- 分批删除

3.2 使用JOIN删除(MySQL专属)

DELETE u FROM users u
JOIN (SELECT 1 AS id UNION SELECT 2) AS tmp
ON u.id = tmp.id

MyBatis实现:

<delete id="batchDelete">
    DELETE u FROM users u
    JOIN (
        <foreach collection="ids" item="id" separator=" UNION ALL ">
            SELECT #{id} AS id
        </foreach>
    ) tmp ON u.id = tmp.id
</delete>

四、JDBC驱动层优化

4.1 启用重写批量语句

# MySQL配置(连接参数)
rewriteBatchedStatements=true

效果对比:

参数状态插入10万条耗时网络请求次数
默认(false)62s10万
启用(true)4.8s1

4.2 批处理参数优化

// 设置JDBC批处理参数
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://host/db?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=500");

五、存储过程批量处理

5.1 Oracle数组参数示例

CREATE TYPE user_array AS TABLE OF user_obj_type;

CREATE PROCEDURE batch_insert_users(
    p_users IN user_array
) AS
BEGIN
    FORALL i IN 1..p_users.COUNT
        INSERT INTO users VALUES p_users(i);
END;

MyBatis调用:

<select id="callBatchInsert" statementType="CALLABLE">
    {call batch_insert_users(#{list,mode=IN,jdbcType=ARRAY})}
</select>

5.2 PostgreSQL批量COPY

Connection conn = sqlSession.getConnection();
CopyManager cp = new CopyManager((BaseConnection) conn);
String data = userList.stream()
        .map(u -> u.getId() + "," + u.getName())
        .collect(Collectors.joining("\n"));
cp.copyIn("COPY users (id,name) FROM STDIN WITH CSV", new StringReader(data));

六、事务管理策略

6.1 批量提交模式

// 每1000条提交一次
int batchSize = 1000;
for (int i = 0; i < total; i++) {
    mapper.insert(list.get(i));
    if (i % batchSize == 0 || i == total - 1) {
        session.commit();
        session.clearCache(); // 清理一级缓存
    }
}

6.2 异常恢复机制

try {
    // 批量操作
} catch (BatchUpdateException e) {
    int[] updateCounts = e.getUpdateCounts();
    for (int i = 0; i < updateCounts.length; i++) {
        if (updateCounts[i] == Statement.EXECUTE_FAILED) {
            log.error("第{}条数据失败: {}", i, list.get(i));
        }
    }
    session.rollback();
    throw new BusinessException("部分数据提交失败");
}

七、性能对比与选型建议

优化方案适用场景10万条耗时内存峰值实现复杂度
foreach拼接小批量简单插入12s1.2GB★☆☆☆☆
BATCH执行器标准OLTP场景3.5s300MB★★☆☆☆
存储过程超大数据量入库1.8s150MB★★★★☆
JDBC批量协议MySQL/Oracle通用场景2.1s200MB★★★☆☆
COPY机制PostgreSQL大数据迁移0.9s50MB★★★★☆

选型建议:

常规业务:BATCH执行器 + 分批次提交
数据迁移:数据库专属批量协议(如MySQL的LOAD DATA)
高并发场景:结合消息队列异步处理
异构数据源:采用Spring Batch框架

八、监控与调优工具

慢SQL日志分析:

# MySQL配置
slow_query_log=1
long_query_time=1
log_output=FILE

JDBC监控指标:

// 获取批处理统计
Statement stmt = connection.createStatement();
int[] counts = stmt.executeBatch();
System.out.println("成功执行数量:" + Arrays.stream(counts).sum());

Arthas诊断:

# 监控批处理方法执行时间
watch com.example.mapper.UserMapper batchInsert '{params,returnObj}' -x 3

通过综合运用上述优化策略,可使批量操作性能得到数量级提升。建议在实际应用中根据具体数据库类型、数据规模、硬件配置等因素进行针对性调优,并建立完善的批量任务监控体系。

解决棋盘放麦子问题不仅需要掌握算法逻辑,还要了解如何在Java中处理大数计算。推荐参考《计算棋盘麦子数量:蓝桥杯Java例题解析》,这本书详细解析了该问题的编程实现和大数处理方法。 参考资源链接:[计算棋盘麦子数量:蓝桥杯Java例题解析](https://wenku.youkuaiyun.com/doc/1rqtcpa3kx?spm=1055.2569.3001.10343) 以下是解决该问题的步骤和代码实现: 1. 首先,需要定义一个BigInteger类型的变量来存储麦粒总数,以避免整数溢出的问题。 2. 接着,使用循环结构遍历棋盘的每一格,每一格上的麦粒数是前一格的两倍。 3. 在每次迭代中,将当前格子的麦粒数累加到总数变量中。 4. 最后,输出累加后的麦粒总数。 示例代码如下:(代码示例略) 在编写代码的过程中,需要注意Java语言的基本语法和API的正确使用,尤其是BigInteger类的使用。这个类提供了大整数运算的支持,可以有效解决大数溢出问题。通过这个问题,你不仅可以练习大数处理,还能加深对Java编程语言特性的理解。 完成该问题的编码后,你可以挑战更多类似的编程题目,提升自己的算法和逻辑思维能力。建议进一步学习Java编程以及在不同场景下如何应用大数处理技巧,如使用BigInteger类解决财务计算中的高精度需求等。 参考资源链接:[计算棋盘麦子数量:蓝桥杯Java例题解析](https://wenku.youkuaiyun.com/doc/1rqtcpa3kx?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值