【问题标题】:Update MySQL from CSV using JAVA使用 JAVA 从 CSV 更新 MySQL
【发布时间】:2017-10-02 09:20:58
【问题描述】:

我的问题如下:

  1. CSV 文件以给定的时间间隔从服务器下载。

  2. 必须解析文件(必须去除不必要的空格)。

  3. 必须为每个条码更新商品价格、建议零售价和库存数量。

每个 CSV 行包含商品编号、产品名称、仓库 ID、仓库名称、价格、rrp 价格、库存和条形码。 items 表包含大约 71000 行。并且条形码不是数据库中的唯一键(因为具有相同条形码的物品可以在不同的仓库中)。问题是在 localhost MySQL 服务器上更新需要一个多小时(SQLite 大约半小时)。有什么方法可以优化 SQL 查询以使事情变得更快?我当前的代码如下所示:

    public void updateData (BufferedReader bufferedReader, Connection sqlConnection){
    String csvLine = null;
    PreparedStatement preparedStatement = null;
    String sqlString = "UPDATE items SET price = ?, rrp = ?, stock = ? WHERE departmentid = ? AND barcode = ?";
    try {
        preparedStatement = sqlConnection.prepareStatement(sqlString);
        while ((csvLine = bufferedReader.readLine()) != null) {
            String[] splitLine = csvLine.split(";");
            preparedStatement.setBigDecimal(1, new BigDecimal(splitLine[4].trim()).setScale(2, RoundingMode.CEILING));
            preparedStatement.setBigDecimal(2, new BigDecimal(splitLine[5].trim()).setScale(2, RoundingMode.CEILING));
            preparedStatement.setInt(3, Integer.parseInt(splitLine[6].trim()));
            preparedStatement.setString(4, splitLine[2].trim());
            preparedStatement.setString(5, splitLine[8].trim());
            preparedStatement.executeUpdate();
        }
    } catch (IOException | SQLException exc) {
        System.out.println(exc.getMessage());
    } finally {
        try {
            sqlConnection.commit();
            preparedStatement.close();
            sqlConnection.close();
        } catch (SQLException exc) {
            exc.printStackTrace();
        }
    }
}

到目前为止,最快的解决方案看起来像 @e4c5 建议的那样,将 CSV 数据加载到临时表并使用查询: UPDATE items INNER JOIN temp_table ON items.barcode = temp_table.barcode SET items.rrp = temp_table.rrp, items.price = temp_table.price, items.stock = temp_table.stock WHERE items.barcode = temp_table.barcode AND items.departmentid = temp_table.departmentid. 有什么方法可以让这更快?

【问题讨论】:

  • 您是否对其进行了分析以查看哪个方面需要时间?确定是executeUpdate()吗?如果是这样,你的表上有索引吗? (顺便说一句,根据这些数字,每次录制需要 0.05 秒,这还不错)。

标签: java mysql performance csv jdbc


【解决方案1】:

有一种更快更好的方式来导入数据,而无需一行 Java 代码。那是LOAD DATA INFILE

请注意,LOAD DATA 可以为您的 CSV 进行一些预处理。然而,在某些情况下它是不够的。然后你必须从 java 获得帮助,但 java 不应该做插入。

您的 java 代码应该预处理 CSV 并生成另一个可以传递给 LOAD DATA 的 CSV。 70000 条记录是轻而易举的事。

LOAD DATA 不支持ON DUPLICATE KEY UPDATE 语法。但它确实有 REPLACE 语法(它比重复键更新稍慢,但它仍然比 java 或任何其他编程语言可以给你的速度至少快一个数量级)。

如果进行实际更新很重要。将数据加载到临时表中,然后到

INSERT INTO mytable SELECT * FROM temp_table ON DUPLICATE KEY ....

【讨论】:

  • 好吧,但问题是更新,而不是插入。你能演示如何使用它来更新数据吗?
  • 这是一项微不足道的任务。请参阅文档顶部的“REPLACE”关键字。
  • LOAD 到一个临时表中,然后运行少量的 SQL 语句(无循环)来处理 dups 和其他无法在 load 语句内部完成的处理。
  • 你好@RickJames 有一段时间了。是的,这也是一个很好的建议。
  • 对不起,但它对我不起作用(或者我做错了)。因为我的主要目标是更新表中已有的内容(不从 csv 添加新内容)并且条形码不是唯一键(产品可以在多个部门中),这就是为什么我有“WHERE departmentid =?和条形码 =?”在我的查询中。
【解决方案2】:

我认为在您的情况下最好的方法是使用语句批处理,这是一个示例:

sqlConnection.setAutoCommit(false);//<<------------
try {
    preparedStatement = sqlConnection.prepareStatement(sqlString);
    while ((csvLine = bufferedReader.readLine()) != null) {
        String[] splitLine = csvLine.split(";");
        preparedStatement.setBigDecimal(1, new BigDecimal(splitLine[4].trim()).setScale(2, RoundingMode.CEILING));
        preparedStatement.setBigDecimal(2, new BigDecimal(splitLine[5].trim()).setScale(2, RoundingMode.CEILING));
        preparedStatement.setInt(3, Integer.parseInt(splitLine[6].trim()));
        preparedStatement.setString(4, splitLine[2].trim());
        preparedStatement.setString(5, splitLine[8].trim());

        preparedStatement.addBatch();//<<-----------add a batch
    }

    //execute your multiple statement as one
    statement.executeBatch();//<<------------
    sqlConnection.commit();//<<--------------
}

编辑

喜欢@Mick Mnemonic 在评论中提到:

如果拆分成较小的批次,例如 500 行,您可以尝试 有什么区别

因此,如果要一次性执行批次,您可以将批次分成小批量,例如:

sqlConnection.setAutoCommit(false);
try {
    int nbrBatch = 500;
    int count = 0;
    preparedStatement = sqlConnection.prepareStatement(sqlString);
    while ((csvLine = bufferedReader.readLine()) != null) {
        //Your code here
        preparedStatement.addBatch();
        if (count % nbrBatch == 0) {
            statement.executeBatch();
        }
        count++;
    }
    //rest of your batch not executed
    statement.executeBatch();
    sqlConnection.commit();
}

【讨论】:

  • 谢谢,这使过程缩短了大约 15 分钟。
  • 如果拆分成较小的批次(例如 500 行)有什么不同,您可以尝试。
  • 是的,这是正确的@MickMnemonic,如果你分成更小的批次,它会更快希望也许我可以用你的评论编辑我的答案,这可能吗?
  • 是的,当然。我想我通常看到的批量大小在几百到一千之间。
  • 在转换 csv(修剪空格等,在使用它之前)然后使用 1000 的批量大小之后,我有大约 15 分钟的总时间来处理 70000 条记录,这是合理的。谢谢大家!
猜你喜欢
  • 1970-01-01
  • 2012-05-01
  • 2017-08-09
  • 2019-06-11
  • 1970-01-01
  • 2012-05-24
  • 2012-05-26
  • 2021-07-25
  • 1970-01-01
相关资源
最近更新 更多