关于Mybatis批量插入使用JDBC原生batch批处理以及mybatis的ExecutorType.BATCH的批处理和Mybatis的动态sql语句批处理

近期处理数据,总结一下

项目背景:从Mongo中定时转移前第7天文本到mysql数据库且生成文件,有各种不同的定时任务,有批次消息、发送明细消息等等很多的数据需要转移,也有老系统和新系统数据的转移,mysql使用的分区表;架构使用的ssm,定时任务使用的xxl-job分布式任务调度

上面都是废话;

1、JDBC原生batch处理400万条数据

/**
     * @Description: 批量新增发送明细数据一次性插入1000条
     * @Param:
     * @return:
     * @Author: fanghuaiming
     * @Date:
     */
    public void savebatchDtlBatch(List<SmsSendHistoryDetail> smsSendHistoryDetails) {
        LOG.info("Fuction:savebatchDtlBatch start insert into mysql batch 【 sms_msg_dtl 】");
        Connection conn = DataSourceUtils.getConnection(dataSource1Config.testDataSource());
        long before = System.currentTimeMillis();
        int count = 0;
        LOG.info("Fuction:savebatchDtlBatch Connection has opend");
        PreparedStatement pstmt = null;
        try {
            pstmt = conn.prepareStatement("INSERT INTO sms_msg_dtl  (unique_id,username,mtype,mobile,batch_id,channel_id,location,words,sub_succ,sub_fail,rpt_succ,rpt_fail,uprice,cprice,submit_time,_id,mdstr,extend) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
            conn.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < smsSendHistoryDetails.size(); i++) {
            try {
                SmsSendHistoryDetail smsSendHistoryDetail = smsSendHistoryDetails.get(i);
                pstmt.setLong(1, null == smsSendHistoryDetail.getUniqueId() ? 0 : smsSendHistoryDetail.getUniqueId());
                pstmt.setInt(2, null == smsSendHistoryDetail.getUserName() ? 0 : smsSendHistoryDetail.getUserName());
                pstmt.setInt(3, null == smsSendHistoryDetail.getMtype() ? 0 : smsSendHistoryDetail.getMtype());
                pstmt.setLong(4, null == smsSendHistoryDetail.getMobile() ? 0 : smsSendHistoryDetail.getMobile());
                pstmt.setLong(5, null == smsSendHistoryDetail.getBatchId() ? 0 : smsSendHistoryDetail.getBatchId());
                pstmt.setInt(6, null == smsSendHistoryDetail.getChannelId() ? 0 : smsSendHistoryDetail.getChannelId());
                pstmt.setString(7, null == smsSendHistoryDetail.getLocation() ? "" : smsSendHistoryDetail.getLocation());
                pstmt.setString(8, null == smsSendHistoryDetail.getWords() ? "" : smsSendHistoryDetail.getWords());
                pstmt.setInt(9, null == smsSendHistoryDetail.getSubSucc() ? 0 : smsSendHistoryDetail.getSubSucc());
                pstmt.setInt(10, null == smsSendHistoryDetail.getSubFail() ? 0 : smsSendHistoryDetail.getSubFail());
                pstmt.setInt(11, null == smsSendHistoryDetail.getRptSucc() ? 0 : smsSendHistoryDetail.getRptSucc());
                pstmt.setInt(12, null == smsSendHistoryDetail.getRptFail() ? 0 : smsSendHistoryDetail.getRptFail());
                pstmt.setLong(13, null == smsSendHistoryDetail.getuPrice() ? 0 : smsSendHistoryDetail.getuPrice());
                pstmt.setLong(14, null == smsSendHistoryDetail.getcPrice() ? 0 : smsSendHistoryDetail.getcPrice());
                pstmt.setLong(15, null == smsSendHistoryDetail.getSubmitTime() ? 0 : smsSendHistoryDetail.getSubmitTime());
                pstmt.setString(16, null == smsSendHistoryDetail.get_id() ? "" : smsSendHistoryDetail.get_id());
                pstmt.setString(17, null == smsSendHistoryDetail.getMdstr() ? "" : smsSendHistoryDetail.getMdstr());
                pstmt.setString(18, null == smsSendHistoryDetail.getExtend()  || "".equals(smsSendHistoryDetail.getExtend()) ? "" : smsSendHistoryDetail.getExtend());
                pstmt.addBatch();
                int a = i + 1;
                if (a % 1000 == 0) {
                    try {
                        pstmt.executeBatch();
                    } catch (BatchUpdateException e) {
                        int[] updateCounts = e.getUpdateCounts();
                        for (int j = 0; j < updateCounts.length; j++) {
                            if(updateCounts[j]<0) {
                                LOG.info("Fuction:savebatchDtlBatch mysql 【 sms_msg_dtl 】 mybe get some exception : {} And this data is : {}",e.getMessage(),smsSendHistoryDetails.get(count * 1000 +j));
                            }
                        }
                    }
                    count += 1;
                    conn.commit();
                    pstmt.clearBatch();
                }
            } catch (
                    SQLException e) {
                LOG.info("Fuction:savebatchDtlBatch mysql 【 sms_msg_dtl 】  batch insert get some Exception : {} ", e);
                e.printStackTrace();
            }
        }
        try {
            try {
                pstmt.executeBatch();
            } catch (BatchUpdateException e) {
                int[] updateCounts = e.getUpdateCounts();
                for (int j = 0; j < updateCounts.length; j++) {
                    if(updateCounts[j]<0) {
                        LOG.info("Fuction:savebatchDtlBatch mysql 【 sms_msg_dtl 】 mybe get some exception : {} And this data is : {}",e.getMessage(),smsSendHistoryDetails.get(count * 1000 +j));
                    }
                }
            }
            conn.commit();
            pstmt.clearBatch();
            count = 0;
        } catch (SQLException e) {
            e.printStackTrace();
            LOG.info("Fuction:savebatchDtlBatch mysql 【 sms_msg_dtl 】  pstmt take executeBatch had SQLException :{}", e);
        } finally {
            try {
                    pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
                LOG.info("Fuction:savebatchDtlBatch mysql 【 sms_msg_dtl 】  pstmt take close had Exception :{}", e);
            }
        }
        long after = System.currentTimeMillis();
        LOG.info("Fuction:savebatchDtlBatch  had done insert into mysql batch 【 sms_msg_dtl 】And Time-consuming : {} milliseconds", after - before);
        DataSourceUtils.releaseConnection(conn, dataSource1Config.testDataSource());
        LOG.info("Fuction:savebatchDtlBatch Connection has released");
    }

2、Mybatis的Batch批处理

    @Async
    @Override
    public void batchInsertBackUp(List<SmsRptDetail> smsSendMsgRpts) {
        try {
            long before = System.nanoTime();
            //连接需要加上 allowMultiQueries=true
            //进行mybaties的批次操作
            SqlSession sqlSession = test1SqlSessionFactory.openSession(ExecutorType.BATCH,false);
            //获取Mapper接口
            SmsSendMsgRptBackUpMapper smsSendMsgRptBackUpMapper = sqlSession.getMapper(SmsSendMsgRptBackUpMapper.class);
            //并行遍历循环新增(非commit操作)
            smsSendMsgRpts.parallelStream().forEach(smsSendMsgRpt ->{
                smsSendMsgRptBackUpMapper.insertSmsSendMsgRpt(smsSendMsgRpt);
            });
            //进行批次提交
            sqlSession.commit();
            //刷新执行结果可以进行断言操作
            List<BatchResult> batchResults = sqlSession.flushStatements();
            //清除缓存
            sqlSession.clearCache();
            //关闭连接
            sqlSession.close();
            long after = System.nanoTime();
            LOG.info("SmsSendMsgRptBackUpServiceImpl:batchInsertBackUp delete count:{} had done and total Millisecond use : {}",batchResults.size(),after - before);
            batchResults.clear();
        } catch (Exception e) {
            e.printStackTrace();
            LOG.info(e.getMessage());
        }
    }
<insert id="insertSmsSendMsgRpt" parameterType="com.sioo.hysms.scheduled.pojo.SmsRptDetail">
        insert into sms_rpt_dtl_backup
        (rpt_time,username,
        mobile,rptcode, unique_id,
        batch_id,channel_id,extend,
        resend_num,resend_count,
        user_sub_time,channel_sub_time,
        resend,uprice,cprice,msgid,
        custom_id,source,create_time,update_time) VALUES
        (#{rptTime},#{username},#{mobile},#{rptcode}, #{uniqueId},#{batchId},
        #{channelId},#{extend},#{resendNum},#{resendCount},#{userSubTime},
        #{channelSubTime},#{resend},#{uprice},#{cprice},#{msgid},#{customId},
        #{source},#{createTime},#{updateTime})
    </insert>

3、Mybatis动态sql语句

    @Async
    @Override
    public void batchInsertBackUps(List<SmsRptDetail> smsSendMsgRpts) {
        try {
            long before = System.currentTimeMillis();
            smsSendMsgRptBackUpMapper.batchInsertBackUp(smsSendMsgRpts);
            long after = System.currentTimeMillis();
            LOG.info("SmsSendMsgRptBackUpServiceImpl:batchInsertBackUps insert had done and total Millisecond use : {}",after - before);
        } catch (Exception e) {
            e.printStackTrace();
            LOG.info(e.getMessage());
        }
    }
<insert id="batchInsertBackUp" parameterType="java.util.List">
        INSERT into sms_rpt_dtl_backup (rpt_time,username,
        mobile,rptcode, unique_id,
        batch_id,channel_id,extend,
        resend_num,resend_count,
        user_sub_time,channel_sub_time,
        resend,uprice,cprice,msgid,
        custom_id,source,create_time,update_time) VALUES
        <foreach collection="smsSendMsgRpts" item="item" index="index" separator=",">
            (#{item.rptTime,jdbcType=BIGINT},#{item.username,jdbcType=INTEGER},#{item.mobile,jdbcType=BIGINT},
            #{item.rptcode,jdbcType=VARCHAR}, #{item.uniqueId,jdbcType=BIGINT},#{item.batchId,jdbcType=BIGINT},
            #{item.channelId,jdbcType=INTEGER},#{item.extend,jdbcType=VARCHAR},#{item.resendNum,jdbcType=INTEGER},
            #{item.resendCount,jdbcType=INTEGER},#{item.userSubTime,jdbcType=BIGINT},#{item.channelSubTime,jdbcType=BIGINT},
            #{item.resend,jdbcType=INTEGER},#{item.uprice,jdbcType=BIGINT},#{item.cprice,jdbcType=BIGINT},
            #{item.msgid,jdbcType=VARCHAR},#{item.customId,jdbcType=VARCHAR},#{item.source,jdbcType=INTEGER},
            #{item.createTime,jdbcType=TIMESTAMP},#{item.updateTime,jdbcType=TIMESTAMP})
        </foreach>

    </insert>

这些部分方法加了异步

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
* @Description: 异步配置对象
* @Param:
* @return:
* @Author: fanghuaiming
* @Date:
*/

@Configuration
@EnableAsync
@PropertySource({"classpath:application-async.properties"})
public class SpringAsyncConfig implements AsyncConfigurer {

    @Value("${springasync.corePoolSize}")
    private int corePoolSize = Runtime.getRuntime().availableProcessors();
    @Value("${springasync.maxPoolSize}")
    private int maxPoolSize = Runtime.getRuntime().availableProcessors();

    /** 
    * @Description: CPU密集型
    * @Param:
    * @return:
    * @Author: fanghuaiming
    * @Date:
    */ 
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setThreadNamePrefix("HysmsScheduled-");
        executor.initialize();
        return executor;
    }
}

效率总结:

1、实际上从每日4000万的数据来看,速度最快的反而是JDBC这种操作,但是如果插入过程中出现什么问题会造成数据库连接卡在这里导致数据库连接不够,因为始终没有归还,从第一个jdbc原生可以看出来我只是捕获了异常而已,也只是业务上的处理,JDBC的连接如果处理最终也只是close掉而不是归还给某个池化的连接,但是不可否认它的速度够快

2、其实我打心眼里是想大规模使用Mybatis的Executor的Batch,它是Mybatis提供的方便简洁美观,但是实际上它的效率很慢,曾经接触过部分了解,这个Batch实际上是有大小的限制的,例如1万条过大的sql拼接到一个类似buffer,这个buffer是有大小限制的,需要你进行清除或者按照第一种JDBC操作的方式达到多少阀值进行一次提交的操作,会有一些坑,这里我只是贴出来保证可以执行,至于效率我并没有优化,可能需要自己进行优化,我因为任务加身就没有继续做了,有兴趣的可以多看看资料或者《深入浅出MyBatis》这个pdf我会传到我的下载里,不知道怎么设置不需要积分,不想花积分可以进我们的讨论群,稍后贴出

3、Mybatis的动态sql语句实际上官方是推崇的,第三种方式我是进行了动态规划做的子任务(使用Fork-Join也是一样的),这个方法只进行300万的数据的插入,我仔细观察过效率(广义上的观察,也就是人工观察?),在前100万条的插入的时候是非常快的,但是后面的300万条数据实际还是很慢的,感觉和Executor的BATCH效率差不多,翻了官方的Mybatis文档也没有介绍详细的效率问题,更多的是如何使用,当然这也可能和它的动态sql拦截器相关执行有关,我没有进行优化了,因为现在任务正常跑,暂时不需要进行优化,还是推荐大家多看看《深入浅出MyBatis》

统一说明:

这里使用的是mysql,如果使用Oracle其实也差不多,改一改一样的,我的mysql使用的是InnoDB数据库引擎,最近很多博客标题都是教你如何3分钟插入1000万条数据都是扯淡,你翻进去一看是MyISAM,那连键锁、临键锁、间隙锁都没有当然快了,为了博噱头什么都不管不顾

还有,我说的4000万条数据中业务会做很多事情,例如转换mongo的数据的判断、聚合、条件的整合还有生成csv文件消耗的大量IO等等业务上的操作,所以我是人工查看的效率而不是代码上进行监控,如果有想做这个监控的可以自己写自定义注解加在方法头上进行AOP切面监控,用的地方用,不用的地方就不加去进行监控

总结:

总的来说我个人比较推崇第一种方法(除了手动关闭连接之外),原生的有时候比较方便,第三方的就需要阅读人家的源码去进行优化了,在以上代码部分有疑问或需要下载pdf的请进讨论:java华夏一区,禁止推销

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Oracle数据库使用MyBatis进行高效的百万级批量插入可以使用批处理操作,以下是实现步骤: 1. 配置MyBatis批处理属性: 在MyBatis的配置文件中添加以下属性: ``` <settings> <setting name="jdbcTypeForNull" value="NULL" /> <setting name="cacheEnabled" value="true" /> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false" /> <setting name="multipleResultSetsEnabled" value="true" /> <setting name="useColumnLabel" value="true" /> <setting name="useGeneratedKeys" value="false" /> <setting name="autoMappingBehavior" value="PARTIAL" /> <setting name="autoMappingUnknownColumnBehavior" value="WARNING" /> <setting name="defaultExecutorType" value="SIMPLE" /> <setting name="defaultStatementTimeout" value="25000" /> <setting name="defaultFetchSize" value="100" /> <setting name="safeRowBoundsEnabled" value="false" /> <setting name="mapUnderscoreToCamelCase" value="false" /> <setting name="localCacheScope" value="SESSION" /> <setting name="jdbcTypeForNull" value="NULL" /> <setting name="flushCacheEnabled" value="false"/> <setting name="batch" value="true"/> </settings> ``` 其中,最后一行 `batch` 属性表示开启批处理模式。 2. 编写Mapper文件: 在Mapper文件中编写批量插入SQL语句,例如: ``` <insert id="batchInsert" parameterType="java.util.List"> insert into user (id, name, age) values <foreach collection="list" item="user" separator=","> (#{user.id}, #{user.name}, #{user.age}) </foreach> </insert> ``` 其中,`parameterType` 指定了传入参数的类型为 `List`,`foreach` 标签表示对传入的List进行遍历,生成批量插入SQL语句。 3. 编写Java代码: 在Java代码中使用MyBatis的 `SqlSession` 对象执行批量插入操作,示例如下: ``` SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = new ArrayList<>(); // 添加一百万条测试数据 for (int i = 0; i < 1000000; i++) { User user = new User(); user.setId(i+1); user.setName("user"+i); user.setAge(20+i); userList.add(user); } userMapper.batchInsert(userList); sqlSession.commit(); ``` 其中,`SqlSessionFactory` 是MyBatis的会话工厂对象,`UserMapper` 是Mapper接口对象,`userList` 是需要批量插入的数据列表,最后需要调用 `commit()` 方法提交事务。 这样,就可以使用MyBatis进行高效的百万级批量插入操作了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值