mybatis使用load data local infile实现大批量导入数据到mysql数据库

时间:2022-02-11 03:47:10

背景:

项目框架为:dubbo+zookeeper+ssm    数据库为mysql

最近有个新的需求,要在代码里实现往数据库插入大批量数据,每次插入的数据量从10万~50万条不等,而且每条数据有80多个字段,大概估算了一下,一条数据大小差不多是1kb,那么每次插入的数据量大小应该在100M~500M之间。这个数据量还是很大的。

想来想去,我就先按照从开发到调试所碰到问题的顺序来写好了,到后面我再贴出代码,供同行们参考。

1、碰到的第一个问题是:

 Packet for query is too large (1139736> 1048576). You can change this value on the server by setting

 the 'max_allowed_packet' variable. 

刚接到需求时,根本没有考虑到数据量的问题,就按照平时开发那样,直接往数据库里执行insert,就报了上面这个错,原来因为mysql有一个max_allowed_packet变量,可以控制其通信缓冲区的最大长度,所以当缓冲区的大小太小的时候,导致某些查询和插入操作报错。 

解决办法:

数据库执行命令 show VARIABLES like '%max_allowed_packet%'; 查看数据库max_allowed_packet变量配置时多少,显示的结果为

+--------------------+---------+ | Variable_name      | Value   | +---------

        -----------+---------+ | max_allowed_packet | 1048576 | +--------------------+---------+

这说明当前的配置时1M,我们需要将其设置大一些。

数据库执行命令  set global max_allowed_packet = 4*1024*1024*10,将值设置为40M,,执行完后,关掉数据库可视化界面,重新打开,要是命令行进行的就重启mysql(不重启的话是不行的,切记重启mysql),然后接着执行命令show VARIABLES like '%max_allowed_packet%';看看有没有设置成功。一般都是可行的!至此,第一个问题解决。

2、碰到的第二个问题是:

com.alibaba.dubbo.remoting.transport.AbstractCodec.checkPayload() ERROR  Data length too large: 11557050, max payload: 8388608 java.io.IOException: Data length too large: 11557050, max payload: 838860

这个错是dubbo相关的,为什么会报这个错呢?想来想去,可能是service服务端读取到的数据量太大,服务端提供给web客户端的数据量就过大,超过了dubbo的默认值8M,错误信息如上所示,天哪,第一次碰到这样的报错,各种查资料,最终还是解决了。

解决办法:

        方法1、 修改提供方的dubbo配置,

            在dubbo.properties 中增加如下

            dubbo.protocol.dubbo.payload=41943040(默认为8M,即8388608)

        方法2、

             在dubbo-provider.xml文件配置如下

            <dubbo:provider id="payload" payload="41943040"/>

    如上两种方法都是将值修改为40M。


3、碰到的第三个问题:

使用mysql的load data local infile往数据库导数据时,英文和数字都正常导入,但是,中文要么不显示,要么就是乱码,真的是搞不懂了,怎么会这样呢?以下是导入部分代码:

   public void batchInsert(List<BqLoan> bqLoanList) throws ClassNotFoundException, SQLException {
        //1000条一提交
        int COMMIT_SIZE=1000;
        //一共多少条
        int COUNT=bqLoanList.size();
        Connection conn= null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.url","CONF_HOME");
            String user = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.username","CONF_HOME");
            String password = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.password","CONF_HOME");
            conn= DriverManager.getConnection(url,user,password);
            conn.setAutoCommit(false);
            String exectuteSql = "load data local infile ''into table bq_loan fields terminated by ','";
            PreparedStatement pstmt = conn.prepareStatement(exectuteSql);
           StringBuilder sb = new StringBuilder();
            for (int i = 0; i < COUNT; i++) {
                sb.append(getTestDataInputStream(bqLoanList.get(i)));
                if (i % COMMIT_SIZE == 0) {
                    InputStream is = null;
                    try {
                        is = new ByteArrayInputStream(sb.toString().getBytes());
                        ((com.mysql.jdbc.Statement) pstmt).setLocalInfileInputStream(is);
                        pstmt.execute();
                        conn.commit();
                        sb.setLength(0);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
            }
            InputStream is = null;
            try {
                is = new ByteArrayInputStream(sb.toString().getBytes());
                ((com.mysql.jdbc.Statement) pstmt).setLocalInfileInputStream(is);
                pstmt.execute();
                conn.commit();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            conn.close();
        }
    }
}
上面代码就是导入部分的一个方法,怎么导中文都不显示,还有些字段中文为乱码,我想肯定是字符集的问题,首先查了下数据库字符集,(查询命令为: show variables like '%char%';),然后看看代码,查询结果显示数据库字符集为utf8,然后百度发现这个导入代码得加上编码格式:
"load data local infile ''into table bq_loan fields terminated by ','"; 

这个加上红色部分编码格式设置后如下,

 "load data local infile ''into table bq_loan character set utf8 fields terminated by ','";

 修改完后再次导入,还是一样,中文不显示,有些字段中文乱码,这就头疼了啊,仔细检查,加上各种百度,才发现代码里自己还挖了个坑,

is = new ByteArrayInputStream(sb.toString().getBytes());

这个将字节数组转换为输入流时,括号里将字符串转换成字节数组时,并没有给定转换后的字节数组的编码格式,所以采用的就是默认的编码格式,我们知道不同编码格式,单个中英文多对应的字节数是不一样的。所以我猜测是这个地方没有设置,导致生成的字节数组编码格式和数据库编码格式不一致,最终导致导数据时中文不显示以及乱码。然后给getBytes()方法加上编码格式,代码如下。

is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));

加上后再进行导入数据,一切顺利,数据一点儿不差的导到库里。


到这里导数就顺利进行了,但是想到以后业务发展壮大时,设置的dubbo的服务端给消费端提供数据量最大值还会不够用,所以就就决定改一下代码,最后和同事讨论,建议采取分批插入,就是调用service服务端时进行分页处理,每页数据量设置为dubbo允许服务端给消费端提供数据量最大值的范围内,然后每次插入数据时,就会进行分批插入,只不过和数据库交互次数相对多几次而已,影响不大。

还有一个,使用"load data local infile"导数据时,我是直接将查询出来的结果(list集合)进行数据的组合,即每条数据的每个字段间使用“,”隔开,每条数据之间使用“/n”换行隔开,最终将每条数据拼接成一个字符串,然后将字符串转换成字节数组并转换成输入流,然后再执行导入操作,再往后就比较简单了。由于我不是通过文件进行导数操作,所以 load data local infile '' into table bq_loan character set utf8 fields terminated by ','"  中红色部分的文件名地址我就不写。

最终的结果是:

        导入1万条数据,用时5.5秒左右

        导入2.6万数据, 用时17.8秒左右

        ......

        导入35万条数据,用时210秒左右

        导入50万条数据,用时305秒左右 

    我这个每条的数据量比较大,一条大概是1kb,所以,感觉速度还行吧,能实现我的需求。     

好了,下面贴出部分代码,供大家对照参考。

public void insertLoanInfo (Map<String,Object> msg) {
        try {
            long startTime = DateOperation.currentTimeMills();
            List<AssetPkgRel> loanList = (List<AssetPkgRel>)msg.get("loanList");
            String pkgName = (String) msg.get("pkgName");
            String pkgCde = (String) msg.get("pkgCde");
            // 备份时间
            String bkTime = DateOperation.convertToDateStr1(DateOperation.currentTimeMills());
            msg.put("bkTime",bkTime);
            if (IS_ONE_KEY_ASSOCIATED.getCode().equals(msg.get("isOneKeyAssociated"))) {
                BqLoanService.deleteByPkgCde(pkgCde);
            }
            List<String> bkList = BqLoanService.selectNumByLoanNo(loanList);
            // 总共的页数
            double totalPage = Math.ceil(bkList.size()/25000.0);
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("loanList",loanList);
            List<BqLoan> list = null;
            for (int i = 1;i <= totalPage;i++) {
                List<BqLoan> bqLoanList = new ArrayList<>();
                map.put("page",i);
                PageInfo<BqLoan> pageInfo = BqLoanService.selectByLoanNo(map);
                list = pageInfo.getList();
                for (int j= 0;j < list.size();j++) {
                    BqLoan BqLoan = list.get(j);
                    BqLoan.setPkgCde(pkgCde);
                    BqLoan.setPkgName(pkgName);
                    BqLoan.setArchTm(bkTime);
                    bqLoanList.add(BqLoan);
                }
                // 将当前页数据插入数据库
                batchInsert(bqLoanList);
                // 当前页插入完之后清空list
                bqLoanList.clear();
            }
            long endTime = DateOperation.currentTimeMills();
            System.out.println("===============插入总时间:"+(endTime-startTime));
        } catch (BusinessException e) {
            logger.error("插入数据异常 "+e.getMessage());
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
public void batchInsert(List<BqLoan> bqLoanList) throws ClassNotFoundException, SQLException {
        //1000条一提交
        int COMMIT_SIZE=1000;
        //一共多少条
        int COUNT=bqLoanList.size();
        Connection conn= null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.url","CONF_HOME");
            String user = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.username","CONF_HOME");
            String password = GetResourceFromProperties.GetResourceFromPropertiesFromfiles("/jdbc.properties","jdbc.password","CONF_HOME");
            conn= DriverManager.getConnection(url,user,password);
            conn.setAutoCommit(false);
            String exectuteSql = "load data local infile ''into table bq_loan character set utf8 fields terminated by ','";
            PreparedStatement pstmt = conn.prepareStatement(exectuteSql);
           StringBuilder sb = new StringBuilder();
            for (int i = 0; i < COUNT; i++) {
                sb.append(getTestDataInputStream(bqLoanList.get(i)));
                if (i % COMMIT_SIZE == 0) {
                    InputStream is = null;
                    try {
                        is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
                        ((com.mysql.jdbc.Statement) pstmt).setLocalInfileInputStream(is);
                        pstmt.execute();
                        conn.commit();
                        sb.setLength(0);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
            }
            InputStream is = null;
            try {
                is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
                ((com.mysql.jdbc.Statement) pstmt).setLocalInfileInputStream(is);
                pstmt.execute();
                conn.commit();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            conn.close();
        }
    }
}

   /**
     *  组装需要插入的数据,字段间以","隔开,每条数据间以"/n"隔开
     */
    public static StringBuilder getTestDataInputStream(BqLoan BqLoan) {
            StringBuilder builder = new StringBuilder();
            builder.append(BqLoan.getSeq());
            builder.append(",");
            builder.append(BqLoan.getLoanNumber());
            builder.append(",");
            builder.append(BqLoan.gettPkgCde());
            builder.append(",");
            builder.append(BqLoan.getPkgName());
            builder.append(",");
            builder.append(BqLoan.getCustemerSeq());
            builder.append(",");
            builder.append(BqLoan.getCustemerName());
            builder.append(",");
            builder.append(BqLoan.getIdType());
            builder.append(",");
            builder.append(BqLoan.getIdNo());
            builder.append(",");
            builder.append(BqLoan.getPhoneNo());
            builder.append(",");
            builder.append("\n");
        return builder;
    }

到此结束,如果有遇到这些问题,然后这篇文章还不能够帮助到你,可以一起再探讨,欢迎骚扰。

刚毕业的小白,欢迎各位同行大佬指导!


本文为原创,欢迎转载,转载请注明出处。 http://blog.csdn.net/pavel101/article/details/79489123

谢谢!