线程中的 PreparedStatement (java)

Posted

技术标签:

【中文标题】线程中的 PreparedStatement (java)【英文标题】:PreparedStatement in Threads (java) 【发布时间】:2022-01-07 02:53:58 【问题描述】:

我正在编写一个将线程插入数据库的程序。 示例

public static void save(String name)

    try(PreparedStatement preparedStatement = ...insert...)
    
        preparedStatement.setString(1, name);
        preparedStatement.executeUpdate();
        preparedStatement.close();
     catch (...)
    

问题:会不会是同时执行insert到表的线程时,一个线程会使用(preparedStatement.executeUpdate())另一个线程的preparedStatement?

【问题讨论】:

【参考方案1】:

当然。您不应该这样做 - 每个线程都需要有自己的数据库连接(因此意味着它必然也有自己的 PreparedStatement)。

更好的是 - 不要这样做。你只是让事情变得混乱和缓慢,这是双输的。您的计划完全没有任何好处。如果同时从 2 个线程插入,数据库不会神奇地更快地完成这项工作。

结论很简单:在将大量数据插入同一个表时,线程是一个非常糟糕的主意,所以不要这样做!

但我真的很想加快我的插入速度!

我的数据收集速度很慢

IF(大如果!!)收集数据以进行插入比数据库可以为您插入记录要慢,并且数据收集工作非常适合多-threading,然后让线程收集数据,但让这些线程将对象放入队列,并有一个单独的“数据库插入器”线程(甚至与数据库有连接的唯一线程)将对象从该队列中拉出运行 INSERT。

如果您可以快速收集数据,或者源不适合多线程,这只会使您的代码更长、更难理解、更难测试和更慢。一点意义都没有。

有用的工具:LinkedBlockingQueue - 它的一个实例是所有线程都拥有的一个共享数据。您的数据收集器线程将对象扔到此队列中,而您的单个 db 插入线程从中获取对象。

一般插入速度建议 1:捆绑

数据库在事务中工作。如果您启用了自动提交模式(并且连接以这种模式启动),那不是“无事务”。这仅仅是(因此得名):数据库在每次事务后提交。您不能在适当的数据库中执行“非事务性”。 commit() 很重(需要很长时间来处理),但过长的事务也是如此(在提交之前在单个事务中执行数千件事)。因此,你得到了金发姑娘的原则:你想运行大约 500 次左右的插入,然后提交。

请注意,这有一个缺点:如果在此过程的中途发生错误,则说明有些记录已提交,有些尚未提交。请记住这一点 - 您的流程需要是幂等的,或者这可能是不可接受的,并且您需要 使其 幂等(例如,通过列出“插入会话”ID 的列,因此您如果操作无法正确完成,可以将它们全部删除) - 如果您的数据库同时被其他东西使用,您还需要更多复杂性(某种标志或过滤器,以便其他同时代码不考虑任何已提交, 插入记录直到整个批次被完全添加)。

相关方法:

con.setAutoCommit(false); con.commit() 这个一般结构:
try (PreparedStatement ps = con.prepare.......) 
  int inserted = 0;
  while (!allGenerationDone) 
    Data d = queue.take();
    ps.setString(1, d.getName());
    ps.setDate(2, d.getBirthDate());
    // set the other stuff
    ps.execute();
    if (inserted++ % 500 == 0) con.commit();
  

con.commit();

一般插入速度建议 2:批量

大多数数据库引擎都有用于批量插入的特殊命令。从数据库引擎的角度来看,在进行批量插入时,各种清理和维护任务需要大量时间,甚至可能不是必需的,或者可以组合起来以节省大量时间。具体来说,检查约束(尤其是参考约束)和构建索引占用了处理INSERT 的大部分时间,这些事情可以完全跳过,也可以通过在最后一次批量执行来大大加快.

执行此操作的方式高度依赖于底层数据库。例如,在 postgres 中,您可以关闭约束检查并关闭索引构建,然后运行插入,然后重新启用。您甚至可以选择完全忽略约束检查(这意味着,如果您的代码混乱,您的数据库可能处于无效状态,但如果速度比安全更重要,这可能是正确的方法)。如果在最后完成,索引构建会相当快。

其他数据库一般都有类似的策略。或者,有一些命令将它们组合在一起,通常称为COPY(而不是INSERT)。检查您的数据库引擎的文档。

阅读this SO question 以获取有关COPYINSERT 比较的一些信息和基准。并使用网络搜索引擎搜索例如mysql bulk insert.

【讨论】:

感谢您的详细解答。我同意。我尝试使用单个线程在最后插入(来自“while”队列的 PrepareStatement),但在 SQLException 之后丢失了数据。我没有深入研究并进行了当前的实施。我将从队列中的一个线程中插入。谢谢!

以上是关于线程中的 PreparedStatement (java)的主要内容,如果未能解决你的问题,请参考以下文章

java中的PreparedStatement.addBatch有啥限制吗?

与 CUDA 中的线程和块并行化

oracle jdbc中的PreparedStatement和setTimestamp

JDBC中的Statement和PreparedStatement的区别

PreparedStatement 中的这些值/参数是啥意思?

PreparedStatement 查询将数据插入表中的特定列