Mysql优化使用LOAD DATA INFILE

今天介绍的是mysql自带的一种批量插入方式且效率更高,通过LOAD DATA LOCAL INFILE实现大批量插入。

MySQL使用LOAD DATA LOCAL INFILE从文件中导入数据比insert语句要快,MySQL文档上说要快20倍左右。
但是这个方法有个缺点,就是导入数据之前,必须要有文件,也就是说从文件中导入。这样就需要去写文件,以及文件删除等维护。某些情况下,比如数据源并发的话,还会出现写文件并发问题,很难处理。
那么有没有什么办法,可以达到同样的效率,直接从内存(IO流中)中导入数据,而不需要写文件呢?

MySQL社区提供这样一个方法:setLocalInfileInputStream(),此方法位于com.mysql.jdbc.PreparedStatement 类中。通过使用 MySQL JDBC 的setLocalInfileInputStream 方法实现从Java InputStream中load data local infile 到MySQL数据库中。

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@Component
public class LoadDataInFileUtil {
    private static final Logger logger = LoggerFactory.getLogger(ShellUtil.class);


    private Connection conn = null;

    @Resource
    private JdbcTemplate jdbcTemplate;
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 将数据从输入流加载到MySQL。
     *
     * @param loadDataSql  SQL语句。
     * @param dataStream   输入流。
     * @param jdbcTemplate JDBC。
     * @return int         成功插入的行数。
     */
    private int bulkLoadFromInputStream(String loadDataSql,
                                        InputStream dataStream,
                                        JdbcTemplate jdbcTemplate) throws SQLException {
        if (null == dataStream) {
            logger.info("输入流为NULL,没有数据导入。");
            return 0;
        }
        conn = jdbcTemplate.getDataSource().getConnection();
        PreparedStatement statement = conn.prepareStatement(loadDataSql);
        int result = 0;
        if (statement.isWrapperFor(com.mysql.jdbc.Statement.class)) {
            com.mysql.jdbc.PreparedStatement mysqlStatement = statement.unwrap(com.mysql.jdbc.PreparedStatement.class);
            mysqlStatement.setLocalInfileInputStream(dataStream);
            result = mysqlStatement.executeUpdate();
        }


        return result;
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 组装 SQL 语句。
     *
     * @param dataBaseName 数据库名。
     * @param tableName    表名。
     * @param columnName   要插入数据的列名。
     */
    public String assembleSql(String dataBaseName, String tableName, String columnName[]) {
        String insertColumnName = StringUtils.join(columnName, ",");
        String sql = "LOAD DATA LOCAL INFILE 'sql.csv' IGNORE INTO TABLE "   + tableName + "(" + insertColumnName + ")";
        return sql;
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 往 StringBuilder 里追加数据。
     *
     * @param builder StringBuilder。
     * @param object  数据。
     */
    public void builderAppend(StringBuilder builder, Object object) {
        builder.append(object);
        builder.append("\t");
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 往 StringBuilder 里追加一条数据的最后一个字段。
     *
     * @param builder StringBuilder。
     * @param object  数据。
     */
    public void builderEnd(StringBuilder builder, Object object) {
        builder.append(object);
        builder.append("\n");
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 通过 LOAD DATA LOCAL INFILE 大批量导入数据到 MySQL。
     *
     * @param sql     SQL语句。
     * @param builder 组装好的数据。
     */
    public int fastInsertData(String sql, StringBuilder builder) {
        int rows = 0;
        InputStream is = null;
        try {
            byte[] bytes = builder.toString().getBytes();
            if (bytes.length > 0) {
                is = new ByteArrayInputStream(bytes);
                //批量插入数据。
                long beginTime = System.currentTimeMillis();
                rows = bulkLoadFromInputStream(sql, is, jdbcTemplate);
                long endTime = System.currentTimeMillis();
                logger.info("LOAD DATA LOCAL INFILE :【插入" + rows + "行数据至MySql中,耗时" + (endTime - beginTime) + "ms。】");
            }
 
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is) {
                    is.close();
                }
                if (null != conn) {
                    conn.close();
                }
            } catch (IOException | SQLException e) {
                e.printStackTrace();
            }
        }
        return rows;
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
}

测试方式:

@SpringBootTest
public class TaskTest {

    // 数据库名。
    public static final String DATA_BASE_NAME = "xx";
    // 表名。
    public static final String TABLE_NAME = "xx";
    // 要插入数据的列名。(必须与插入的数据一一对应)
    public static final String COLUMN_NAME[] = {"id", "feature_name", "feature_desc"};

    @Autowired
    LoadDataInFileUtil loadDataInFileUtil;


    @Test
    public void insert() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        String name="xx";

        StringBuilder sb = new StringBuilder();
        List<Test> list = new ArrayList<>();
        for(int i = 1; i < 100000; i++) {
            loadDataInFileUtil.builderAppend(sb, UUID.randomUUID().toString());
            loadDataInFileUtil.builderAppend(sb, i);
            loadDataInFileUtil.builderEnd(sb, name + i);
        }


         String sql = loadDataInFileUtil.assembleSql(DATA_BASE_NAME, TABLE_NAME, COLUMN_NAME);
        int insertRow = loadDataInFileUtil.fastInsertData(sql, sb);
        System.out.println("insert应收报表数量insertRow:"+insertRow);


        stopWatch.stop();
        System.out.println("花费时间" + stopWatch.getTotalTimeSeconds());



        System.out.println("---------方法执行结束--------------");
    }

经过测试插入1w条数据时候与拼装批量插入语句时间差别不大,当插入数量达到10w出现了明显的时间差:

拼装批量插入语句花费时间:6.83s

LOAD DATA LOCAL INFILE实现大批量插入花费时间:1.23s

当表格的字段更多数据量更大出现的时间差就越大。

总结:当需要进行大批量数据插入的时候,可以优先考虑LOAD DATA LOCAL INFILE实现方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值