【问题标题】:Bulk insert in Java using prepared statements batch update使用准备好的语句批量更新在 Java 中批量插入
【发布时间】:2011-07-31 20:28:30
【问题描述】:

我正在尝试用大约 50,000 行 10 列填充 Java 中的结果集 然后使用PreparedStatementbatchExecute方法将它们插入到另一个表中。

为了加快处理速度,我做了一些研究,发现在将数据读入 resultSet 时,fetchSize 起着重要作用。

非常低的 fetchSize 会导致过多的服务器访问,而非常高的 fetchSize 会阻塞网络资源,因此我进行了一些试验并设置了适合我的基础架构的最佳大小。

我正在阅读这个结果集并创建插入语句以插入到不同数据库的另一个表中。

类似这样的东西(只是一个示例,不是真正的代码):

for (i=0 ; i<=50000 ; i++) {
    statement.setString(1, "a@a.com");
    statement.setLong(2, 1);
    statement.addBatch();
}
statement.executeBatch();
  • executeBatch 方法会尝试一次发送所有数据吗?
  • 有没有办法定义批量大小?
  • 有没有更好的方法来加快批量插入的过程?

在批量更新时(50,000 行 10 列),使用可更新的 ResultSet 还是使用 PreparedStaement 批量执行更好?

【问题讨论】:

    标签: java resultset bulkinsert prepared-statement


    【解决方案1】:

    我会依次解答你的问题。

    • executeBatch 方法会尝试一次发送所有数据吗?

    这可能因每个 JDBC 驱动程序而异,但我研究过的少数驱动程序将遍历每个批处理条目,并每次将参数与准备好的语句句柄一起发送到数据库以执行。也就是说,在上面的示例中,将使用 50,000 对参数执行 50,000 次准备好的语句,但是这 50,000 个步骤可以在较低级别的“内部循环”中完成,这是节省时间的地方。作为相当延伸的类比,这就像从“用户模式”退出到“内核模式”并在那里运行整个执行循环。您节省了为每个批次条目进出该较低级别模式的成本。

    • 有没有办法定义批量大小

    在通过Statement#executeBatch() 执行批处理之前,您已在此处通过推入 50,000 个参数集来隐式定义它。一个批量大小同样有效。

    • 有没有更好的方法来加快批量插入的过程?

    考虑在批量插入之前显式打开事务,然后再提交。不要让数据库或 JDBC 驱动程序在批处理中的每个插入步骤周围强加事务边界。您可以使用Connection#setAutoCommit(boolean) 方法控制JDBC 层。首先将连接退出自动提交模式,然后填充您的批处理,启动事务,执行批处理,然后通过Connection#commit() 提交事务。

    此建议假定您的插入不会与并发写入者竞争,并假定这些事务边界将为您提供从源表中读取的足够一致的值,以便在插入中使用。如果不是这种情况,请优先考虑正确性而不是速度。

    • 使用可更新的ResultSetPreparedStatement 执行批处理更好吗?

    没有什么比使用您选择的 JDBC 驱动程序进行测试更好的了,但我希望后者 — PreparedStatementStatement#executeBatch() 会在这里胜出。语句句柄可能有一个关联的“批处理参数”列表或数组,每个条目都是在调用Statement#executeBatch()Statement#addBatch()(或Statement#clearBatch())之间提供的参数集。该列表将随着每次调用addBatch() 而增长,并且在您调用executeBatch() 之前不会被刷新。因此,Statement 实例实际上充当了参数缓冲区;您为了方便而交换内存(使用 Statement 实例代替您自己的外部参数集缓冲区)。

    同样,只要我们不讨论特定 JDBC 驱动程序,您应该将这些答案视为一般性和推测性的。每个驱动程序的复杂程度各不相同,每个驱动程序追求的优化也各不相同。

    【讨论】:

    • 谢谢,非常有趣的指点。无疑提高了我对 JDBC 的理解。会从这里拿来做一点实验
    【解决方案2】:

    批处理将“一次性”完成 - 这就是您要求它执行的操作。

    50,000 似乎在一个呼叫中尝试有点大。我会把它分成 1,000 个的小块,如下所示:

    final int BATCH_SIZE = 1000;
    for (int i = 0; i < DATA_SIZE; i++) {
      statement.setString(1, "a@a.com");
      statement.setLong(2, 1);
      statement.addBatch();
      if (i % BATCH_SIZE == BATCH_SIZE - 1)
        statement.executeBatch();
    }
    if (DATA_SIZE % BATCH_SIZE != 0)
      statement.executeBatch();
    

    50,000 行不应超过几秒钟。

    【讨论】:

    • 谢谢,我会按照你的建议去做,但这将是我的最后一个选择,我在 JDBC API 中寻找一些内置功能,我可以像我们设置的那样设置某种 Batch-Size批量读取时的 Fetch-Size,如果没有,那么不提供批量执行的批量大小但允许 FetchSize 批量读取的原因是什么
    • @Bohemian ,为 DATA_SIZE 考虑的理想值是多少?
    • @BindumaliniKK 根据我的经验,10K 是一个不错的起点。要对其进行调整,请不断提高它,直到它停止可靠工作,然后使用该值的一半。
    【解决方案3】:

    如果只是将数据库中一个/多个表中的数据插入到该表中并且没有干预(对结果集的更改),则调用statement.executeUpdate(SQL)执行INSERT-SELECT语句,这更快,因为没有开销。没有数据离开数据库,整个操作都在数据库上而不是在应用程序中。

    【讨论】:

    • 正如我在问题中提到的,我正在插入不同数据库中的不同表,因此插入选择是不可能的,因为我没有任何指向源的 DBlinks
    【解决方案4】:

    批量未记录的更新不会以您想要的方式为您提供所需的改进性能。见this

    【讨论】:

      猜你喜欢
      • 2011-11-03
      • 1970-01-01
      • 2015-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2022-12-21
      相关资源
      最近更新 更多