多线程数据库阅读

时间:2013-11-23 18:09:12

标签: java multithreading oracle jdbc

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

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

我们为每个线程提供了不同的select语句,例如select count(*) from "+table+ " where id like '"+format + "%'"

好的听起来读取rowid模式很好,但是通过rowid或rownum读取是否很好?

任何机构都可以采用新方法的优点和缺点,还有其他方法可以实现它。

3 个答案:

答案 0 :(得分:2)

网络

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

考虑到你有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

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

由于这一点以及当你删除行时你在rowid序列中有一个“洞”这一事实,rowid可能在表记录中不是平均分配的。

因此,例如,如果你有三个线程并且每个获取1'000'000 rowid,则有可能一个将获得1'000'000个记录,而另外两个1个记录。因此,一个人将不堪重负,而另外两个饿死

在您的情况下,这可能不是什么大问题,但很可能是您目前面临的主键模式问题。

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

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

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

所以拥有自己的“分片”列会好得多,但是你必须确保自己以大致相等的块分割记录。


另外请记住,如果要在多个线程中执行此操作,请务必检查锁定模式数据库使用,或许只是为每次访问锁定表,然后多线程是没有意义的

正如其他人所建议的那样,你最好先找到性能低下的主要原因(网络,磁盘,数据库锁定,线程饥饿,或者你可能只有次优查询 - 检查查询计划)。

答案 1 :(得分:1)

  1. 创建一个调度线程,读取N行的PK(ID)。 在这里你可以进行某种缓存 - 读取N = 1000行,将它们提供给Worker1, 读下一个N = 1000行,将它们提供给Worker2等。这样,你就不需要了 在调度程序线程中保留超过N = 1000个ID(PK)的内存。一旦您 将工作(工作是这些N = 1000个ID)传递给Worker线程, 你把它们放在调度员线程中(不需要保留它们)。

  2. 每个工作线程获取其N(例如1000)PKs / ID并使用它们 从DB获取行。这里确保你使用rowlock(T-SQL) 或者等效的,如果你不使用SQL Server。这样,线程 不会进入彼此的方式。所以worker从DB中读取N行 并处理它们。一旦完成,它可以向调度员发出信号 (像“我完成了”事件)。

  3. 这是我想到的最初想法。我猜 如果你想更多的话,可以进一步完善。

答案 2 :(得分:1)

正如评论中所指出的,多线程可能没有帮助,甚至可能使事情变得更糟。

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

  1. 查看查询计划是什么,看看重新调整SQL是否可以改进 查询计划。
  2. 在数据库方面进行更多处理 - 但由于您正在进行SELECT COUNT...,因此您无法在此处执行任何操作!
  3. 看看您是否可以根据自上次运行以来的新行或更新行重新执行incremental computation查询,而不是每次都查询所有旧数据。
  4. 这些都不能保证成功。这取决于你想要做什么。