【问题标题】:Multi threading database reading多线程数据库读取
【发布时间】:2013-11-23 18:09:12
【问题描述】:

在我们的 Java 应用程序中,我需要从 oracle 数据库中读取大约 8000 万条记录。我正在尝试为此重新设计多线程程序。目前我们正在使用 Java 5 线程池,其中 10 个线程基于主键模式并行读取数据库。每个线程将读取不同的模式,例如 001* 和 002*。

如何提高该程序的性能?我正在考虑设计模式,让线程读取数据库并将处理委托给子线程。在我们现有的设计中,不同的线程通过 10 个 jdbc 连接来访问表。使用新方法,我将只有一个线程读取表格。

我们对每个线程都有不同的选择语句,例如 select count(*) from "+table+ " where id like '"+format + "%'"

好吧,读 rowid 模式听起来不错,但是通过 rowid 或 rownum 来读好吗?

任何机构都可以请教新方法的优缺点吗?还有其他方法可以实现吗?

【问题讨论】:

  • 在不知道瓶颈的来源的情况下,重新设计它可能会使问题变得更糟,也可能变得更好。我建议您花更多时间来确定为什么下载数据需要这么长时间,并设计您的软件来解决这个问题。
  • 你可能不会从线程中获得很大的性能提升,因为数据库操作可能会受到 IO 限制。我将从完全删除线程开始。
  • 确实 - 如果您将数据库存储在单个物理硬盘驱动器上,多线程读取可能只会使问题变得更糟,因为它可能会导致更多的磁盘寻道。
  • 除非你相当确定它是均匀分布的,否则不要按“模式”分割。最好在 rowid 上按块拆分,以便您挑选出在磁盘上靠得很近的信息,并且通常会非常均匀地拆分大卷。你是分块还是只运行 10 个线程,每个线程都有一个选择?
  • 感谢 cmets,我已经修改了问题的更多细节。

标签: java multithreading oracle jdbc


【解决方案1】:

网络

首先,由于使用 rowidrownum 无论如何都是供应商锁定的,因此您应该考虑使用数据库存储的例程。它可以显着减少从数据库传输数据到应用程序服务器的开销(特别是如果它们在不同的机器上并通过网络连接)。

考虑到您有 8000 万条记录要传输,这可能是对您的最佳性能提升,尽管这取决于您的线程所做的工作类型。

显然增加带宽也有助于解决网络问题。

磁盘性能

在更改代码之前检查任务运行时的硬盘负载,也许它无法处理那么多 I/O(同时读取 10 个线程)。

迁移到 SSD/RAID 或集群数据库可能会解决此问题。虽然在这种情况下不会更改访问数据库的方式。

多线程可以解决CPU问题,但数据库主要依赖磁盘系统。

Rownum

如果您将使用 rowid 和 rownum 实现它,您可能会遇到一些问题。

1) rownum 是为每个查询的结果动态生成的。所以如果查询没有明确的 排序,并且每次运行查询时某些记录可能具有不同的 rownum。

例如,您第一次运行它并得到如下结果:

some_column | rownum
____________|________
     A      |    1
     B      |    2
     C      |    3

然后你第二次运行它,因为你没有明确的排序,dbms(出于某种自己知道的原因)决定返回这样的结果:

some_column | rownum
____________|________
     C      |    1
     A      |    2
     B      |    3

2) 第 1 点还意味着,如果您要过滤 rownum 上的结果,它将生成带有 ALL 结果的临时表,然后过滤它

所以 rownum 不是拆分结果的好选择。虽然 rowid 看起来更好,但它也有一些问题。

罗伊德

如果您查看ROWID description,您可能会注意到“rowid 值唯一标识数据库中的一行”。

因此,当您删除一行时,您在 rowid 序列中有一个“洞”,因此 rowid 可能在表记录之间分布不均。

因此,例如,如果您有三个线程并且每个线程获取 1'000'000 个 rowid,那么一个线程可能会获得 1'000'000 条记录,而另外两个线程则各有 1 条记录。所以一个人会不知所措,而另外两个人挨饿

在您的情况下可能没什么大不了的,尽管这很可能是您目前在使用主键模式时面临的问题。

或者,如果您首先在调度程序中获取所有 rowid,然后将它们平均分配(如 peter.petrov 建议的那样),这可以做到这一点,尽管获取 8000 万个 id 听起来仍然很多,但我认为这样做会更好使用一个返回块边界的 sql 查询进行拆分。

或者您可以通过为每个任务提供少量的 rowid 并使用 Java 7 中引入的 Fork-Join 框架来解决这个问题,但是它should beusedcarefully

同样明显的一点:rownum 和 rowid 都不能跨数据库移植。

所以最好有自己的“分片”列,但你必须确保自己将记录分成或多或少相等的块。


另外请记住,如果您要在多个线程中执行此操作,请务必检查数据库使用的锁定模式,也许它只是为每次访问锁定表,那么多线程毫无意义.

正如其他人建议的那样,您最好先找出导致性能低下的主要原因(网络、磁盘、数据库锁定、线程不足,或者您可能只是有次优查询 - 检查查询计划)。

【讨论】:

  • 感谢您提供详细信息,我正在努力解决此问题,一旦我发现一些可以提高性能的神奇事物,就会回来。
【解决方案2】:
  1. 创建一个读取 N 行的 PK(ID)的调度线程。 在这里您可以进行某种缓存 - 读取 N=1000 行,将它们提供给 Worker1, 读取下 N=1000 行,将它们交给 Worker2 等。这样,您不需要 在调度程序线程的内存中保留超过 N=1000 个 ID (PK)。一旦您 将工作(工作就是这些 N=1000 个 ID)传递给 Worker 线程, 您将它们放在调度程序线程中(无需保留它们)。

  2. 每个工作线程获取其 N(例如 1000)个 PK/ID 并使用它们 从数据库中获取行。确保在此处使用行锁 (T-SQL) 或者如果您不使用 SQL Server,则使用它的等价物。这样,线程 不会妨碍对方。所以worker从数据库中读取了N行 并处理它们。一旦完成,它可能会向调度员发出信号 (类似于“我完成了”事件)。

这是我想到的最初想法。我猜 如果您对此有更多想法,可以进一步完善。

【讨论】:

  • 谢谢,我可以分发基于行的 oracle rowid 或 rownum ,哪个是更好的选择?
  • 好吧,我对 Oracle 不是很熟悉,抱歉。好久没玩了。
  • 这个想法无法扩展。虽然扩大规模会有所帮助..但资源限制会出现..
【解决方案3】:

如 cmets 中所述,多线程可能无济于事,甚至可能使事情变得更糟。

任何查询的标准替代方案是:

  1. 查看查询计划是什么,看看重新调整 SQL 是否可以改进 查询计划。
  2. 在数据库端进行更多处理 - 但由于您正在执行 SELECT COUNT...,因此您无能为力!
  3. 看看您是否可以根据自上次运行以来的新行或更新行重新处理查询以执行 incremental computation,而不是每次都查询所有旧数据。

这些都不能保证成功。这取决于您要做什么。

【讨论】:

    猜你喜欢
    • 2018-08-17
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-27
    • 1970-01-01
    • 2018-01-10
    相关资源
    最近更新 更多