批量插入的一万种场景之二

在日常工作中,我们经常会遇到各种各样的需求,需要将数据插入到数据库中,不过不同的需求用不同的插入写法,这样能提升很大的效率,这篇文章会根据不同的场景使用不同的批量插入代码,大家以后遇到批量插入直接来我这里粘贴就可以了。

 

场景一:

app中最常见的pv和uv接口,用户每点击一次都需要在数据库中插入一条数据。这种情况我们不可能一直请求数据库,肯定会将数据先存到缓存或者队列中,然后批量插入到数据库。

 

上队列代码

public void run() {
    startLog();
    while (!isInterrupted()) {
        List<T> list = new ArrayList<T>();
        preProcessor();
        try {
            long ts = 0;
            //每一百条批量插入一次
            for (int i = 0; i < 100; ) {
                T tmp = null;
                try {
                    tmp = queue.poll(5000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tmp != null) {
                    //记录第一次添加数据后的时间
                    if (i++ == 0) {
                        ts = System.currentTimeMillis();
                    }
                    //处理
                    processor(list, tmp);
                    continue;
                }
                //有数据  且从第一次插入数据已经过了 设定时间 则 执行插入
                if (list.size() > 0 && System.currentTimeMillis() - ts >= BATCH_TIMEOUT) {
                    break;
                }
            }
            save(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

上mybatis代码

<insert id="batchInsert" parameterType="List">
    insert into dsc_user 
    (
        user_name, nick_name, mobile
    )
    values
    <foreach collection='list' item='item' index='index' separator=','>
        (
            #{item.userName,jdbcType=VARCHAR},#{item.nickName,jdbcType=VARCHAR},#{item.mobile,jdbcType=VARCHAR}
        )
    </foreach>
</insert>

注:这个地方不能把foreach标签写到外层,否则还是会不停的创建关闭sqlsession

 

场景二:

某个功能导入数据总是千万级别,如何最快的将数据插入到数据库中

 

这种大数据量导入数据 和 小数据量导入是有很大区别的,同样的代码,小数据量导入可以完美实现,但数据量大了之后就会出现各种各样的问题

 

首先,我们要重视的是内存,如果要实现千万级别的数据导入,JVM的堆内存一定要设置一个比较大的数值

其次,要分段保存到数据库,比如每1000条数据执行一次

最后,不要使用mybatis,因为myabtis封装程度太高,解析SQL模板和SQL拼接会消耗大量时间,要使用原生的JDBC

 上代码

public void insertBatch() {
    Connection conn = null;
    Statement statement = null;
    try {
        conn = DriverManager.getConnection(url, username, password);
        conn.setAutoCommit(false);
        statement = conn.createStatement();
        for (int i = 0; i < 10000000; i++) {
            String sql = "insert into dsc_user(user_name, nick_name, mobile)  
                          values ('"+i+"', '第"+i+"条数据', '158'+"+ i +")";
            //利用addBatch方法将SQL语句加入到stmt对象中
            statement.addBatch(sql);
            if (i % 1000 == 0 && i != 0) {
                //利用executeBatch方法执行1000条SQL语句
                statement.executeBatch();
                statement.clearBatch();
                conn.commit();
            }
        }
        statement.executeBatch();
        statement.clearBatch();
        conn.commit();
        close(); //关闭资源
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

一般情况下,我们很少遇到需要批量插入千万级别数据的,所以批量插入只需要在mybatis中使用一下foreach标签就能解决,如下代码

public void insertBatch(List<CouponHistory> coupons){
    if (coupons == null || coupons.isEmpty()){
        return;
    }
    int index = 1000;
    int currentPageStart = 0;
    int currentPageEnd = 0;

    while (currentPageEnd < coupons.size()){
        currentPageStart = currentPageEnd;
        currentPageEnd = Math.min(currentPageEnd + index, coupons.size());
        //调用mybatis批量插入
        batchInsert(coupons.subList(currentPageStart, currentPageEnd));
    }
}
<insert id="batchInsert" parameterType="List">
    insert into dsc_user 
    (
        user_name, nick_name, mobile
)
    values
    <foreach collection='list' item='item' index='index' separator=','>
        (
            #{item.userName,jdbcType=VARCHAR},#{item.nickName,jdbcType=VARCHAR},#{item.mobile,jdbcType=VARCHAR}
        )
    </foreach>
</insert>

 

大家在做批量插入时只需要将最后这块代码复制过去就可以啦

注:mybatis-plus中批量插入方法就是 for循环单条插入;

 

 

以上就是批量导入两个场景中的解决方案,今天就写到这里,再见啦!

 

 

 

昔我往矣,杨柳依依

今我来思,雨雪霏霏

【白话译文】

回想当初出征时,杨柳依依随风吹;如今回来路途中,大雪纷纷满天飞

 

posted @ 2021-07-13 13:54  博客小屋  阅读(79)  评论(0编辑  收藏  举报