【问题标题】:Reading from Database through multiple threads in java通过java中的多个线程从数据库中读取
【发布时间】:2017-01-28 22:12:14
【问题描述】:

我正在使用 java 中的多个线程从 vertica 数据库中读取数据。 我有大约 2000 万条记录,并且正在打开 5 个不同的线程,这些线程具有这样的选择查询......

start = threadnum;

while (start*20000<=totalRecords){

    select * from tableName order by colname limit 20000 offset start*20000.

    start +=5;

}

上面的查询分配了 20K 个不同的记录来从 db 读取到每个线程。 例如,第一个线程将读取前 20k 条记录,然后从 100 000 个位置开始读取 20K 条记录,等等

但我没有得到性能提升。事实上,如果使用单线程读取 2000 万条记录需要 x 秒,那么每个线程从数据库读取几乎需要 x 秒。 不应该比 x 秒(预期 x/5 秒)有一些改进吗?

谁能指出问题出在哪里?

【问题讨论】:

  • 按照这个逻辑,您只需将线程数增加n,即可将总处理时间减少1/n
  • 网络不是多线程的。您可以使用任意数量的线程,但是一旦网络饱和,就是这样,不可能有进一步的改进。

标签: java multithreading


【解决方案1】:

除了您了解多线程可以改善哪些情况以及不可以改善哪些情况之外,没有任何问题。

您的数据库可能位于磁盘上;那是一个带有一组磁头的磁盘,它们都一致地移动,所以它与说它是一个单磁头的磁盘是一样的。头部从一个位置移动到另一个位置需要时间;这称为寻道时间

当您从一个线程读取顺序数据时,磁头必须在轨道之间移动很少。

当您从多个线程读取不同的顺序数据流时,磁头必须移动很多才能从一个轨道跳到另一个很远的轨道,然后再回到第一个轨道。这需要大量的搜索开销。

当然,您的硬盘使用单根电缆连接到您的主板,因此所有数据(在所有寻道开销之后)都必须通过它,然后才能由您的不同线程处理。

结果当然是性能很差。

要带回家的教训是这样的:

多线程永远无法改善来自同一设备的大量 I/O。

换句话说:当所有数据都来自单个顺序源时,处理数据的并行性永远不会提高性能。

如果您将 5 个不同的数据库存储在 5 个不同的磁盘上,那会更好。 (如果您还将这些磁盘连接到主板上的 5 个独立 IDE 控制器,那就更好了。)

【讨论】:

    【解决方案2】:

    我不会重复 Mike Nakis 所说的话,因为它是真实的并且解释得很好:

    多线程无法改善物理磁盘的 I/O

    不过我还是想补充一点。

    当你执行这样的查询时:

     select * from tableName order by colname limit 20000 offset start*20000.
    

    您可以从客户端处理查询结果,您可以通过使用多个线程来改进这些结果。

    但从数据库方面来看,您并没有亲自处理查询,而 Vertica 数据库可能旨在通过根据机器可能性执行并行任务来执行您的查询。

    因此,从客户端您可以将查询的执行拆分为一个、两个或三个并行线程,它最终不应该改变很多事情,因为专业数据库旨在根据它的请求数量优化响应时间接收和加工可能性。

    【讨论】:

      【解决方案3】:

      不,你不应该得到 x/5 秒。您没有考虑在相同的时间内获得 5 倍的记录数这一事实。重要的是吞吐量,而不是时间。

      【讨论】:

        【解决方案4】:

        在我看来,以下是一个很好的解决方案。它使我们能够流式传输和处理数百万条记录,而无需太多内存和处理开销。

        PreparedStatement pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        pstmt.setFetchSize(Integer.MIN_VALUE);
        ResultSet rs = pstmt.executeQuery();
        while(rs.next()) {
            // Do the thing
        }
        

        使用OFFSET x LIMIT 20000 将导致同一个查询一次又一次地执行。对于 2000 万条记录和每次执行 20K 条记录,查询将执行 1000 次。 OFFSET 0 LIMIT 20000 会表现良好,但 OFFSET 19980000 LIMIT 20000 本身会花费很多时间。由于查询将被完全执行,然后从顶部开始,它必须忽略 19980000 条记录并给出最后的 20000 条记录。

        但是使用ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY 选项并将获取大小设置为Integer.MIN_VALUE 将导致查询仅执行一次,并且记录将分块流式传输,并且可以在单个线程中处理。

        【讨论】:

          猜你喜欢
          • 2016-06-27
          • 2018-01-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-27
          • 2019-01-17
          • 1970-01-01
          • 2018-05-27
          相关资源
          最近更新 更多