如何在Java中实现用于下载单个表数据的多个线程



如何实现具有多个/相同连接的多个线程,以便可以快速下载单个大表数据

实际上,在我的应用程序中,我正在下载一个有12个lac (1 lac = 100,000)记录的表,在正常连接速度下至少需要4小时下载,连接缓慢时需要更多小时。

因此,需要在Java中实现几个线程来下载具有多个/相同连接对象的单个表数据。但是不知道怎么做。

如何在几个线程中定位一个记录指针,然后如何将所有线程记录添加到一个大文件中?

Thanks in Advance

首先,这样庞大的数据获取下载到客户端是不是不可取?如果你需要数据显示的目的,那么你不需要更多的记录,以适应你的屏幕。您可以对数据进行分页并每次获取一页。如果你在你的内存中获取和处理它,那么你肯定会在你的客户端上耗尽内存。

如果你需要这样做,而不考虑这个建议,那么你可以产生多个线程,每个线程将提取一小部分数据(1到许多页)。如果您有100K条记录和100个线程可用,那么每个线程可以提取1K的记录。同样,不建议使用100个线程和100个打开的数据库连接。这只是一个例子。将no线程数限制为某个最优值,并限制每个线程提取的记录数。您可以根据rownum限制从DB中提取的记录的数量。

正如Vikas所指出的那样,如果你正在向客户端下载1gb的数据,那么你正在做一些非常非常错误的事情,正如他所说的那样,你永远不需要下载更多可以容纳在屏幕上的记录。但是,如果只是为了数据库复制或备份而偶尔需要这样做,则只需使用DBMS的数据库导出功能并使用DAP(或您喜欢的下载加速器)下载导出的文件。

似乎有多种方法可以"从一个完整的表中多线程读取"。

第零方式:如果你的问题只是"我用完RAM读取整个表到内存中",那么你可以尝试以某种方式一次处理一行(或一批行),然后处理下一批,等等。这样可以避免将整个表加载到内存中(但仍然是单线程,因此可能很慢)。

第一种方法:让一个线程查询整个表,将单个行放到一个队列中,该队列为多个工作线程提供服务[注意,如果您希望第一个线程尽可能快地运行,设置JDBC连接的读取大小可能会有所帮助]。缺点:一次只有一个线程查询初始数据库,这可能不会使数据库本身"最大化"。优点:你没有重新运行查询,所以排序顺序不应该在你中途改变(例如,如果你的查询是select * from table_name,返回顺序是随机的,但如果你从相同的结果集/查询返回它,你不会得到重复)。你不会有意外的重复或类似的东西。这里有一个这样做的教程。

第二种方式:分页,基本上每个线程都知道它应该选择哪个块(在这个例子中是XXX),所以它知道"我应该像select * from table_name order by something start with XXX limit 10一样查询表"。然后每个线程基本上一次处理10个进程(在这个实例中)[XXX是线程间的共享变量,由调用线程递增]。

问题在于"按某物排序",这意味着对于每个查询,DB必须对整个表进行排序,这可能是可能的,也可能是不可能的,并且可能是昂贵的,特别是在表的末尾。如果它是索引,这应该不是一个问题。这里的警告是,如果数据中存在"空白",您将执行一些无用的查询,但它们可能仍然很快。例如,如果你有一个ID列,并且它大部分是连续的,你可能能够基于ID"块"。

如果你有其他一些列可以切断,例如日期每约会一个已知的"数量"列,索引,那么你也许能够避免"命令",而不是将按日期,例如select * from table_name where date < XXX and date > YYY(也没有限制条款,尽管你可以有一个线程使用限制条款来解决一个特定的独特的日期范围,更新它或排序和组块,因为它是一个较小的范围内,减少疼痛)。

第三种方式:执行查询以"保留"表中的行,如update table_name set lock_column = my_thread_unique_key where column is nil limit 10,然后执行查询select * from table_name where lock_column = my_thread_unique_key。缺点:您确定您的数据库将此作为一个原子操作执行吗?如果不是,那么两个setter查询可能会发生冲突或类似的情况,导致重复或部分批处理。小心些而已。也许同步您的进程周围的"选择和更新"查询或锁定表和/或行适当。这样可以避免可能的冲突(例如postgres需要特殊的SERIALIZABLE选项)。

第四种方法:(与第三种方法相关)如果你有很大的间隙,并且想要避免"无用"的查询,那么最有用:创建一个新表,用一个递增的ID"编号"你的初始表[基本上是一个临时表]。然后,您可以将该表划分为连续的ID块,并使用它来引用第一个表中的行。或者如果你已经在表中有一列(或者可以添加一个)用于批处理目的,你可以将批ID分配给行,如update table_name set batch_number = rownum % 20000,然后每一行都有一个批编号分配给自己,线程可以分配批(或分配"每9批"或什么不是)。或者类似的update table_name set row_counter_column=rownum (Oracle的例子,但是你明白了)。然后你就有了一组连续的数字来批处理。

第五种方法:(不确定我是否真的推荐这种方法,但是)在插入时为每一行分配一个"随机"浮点数。然后,如果你知道数据库的大致大小,你可以剥离它的一小部分,比如,如果100,你想要100批,其中x <0.01和X>= 0.02"或类似的。(灵感来自维基百科如何能够获得一个"随机"页面——在插入时为每一行分配一个随机浮点数)。

你真正想要避免的是在进行到一半时改变了排序顺序。例如,如果您没有指定排序顺序,并且只是从多个线程中查询像这样的select * from table_name start by XXX limit 10,那么可以想象,数据库将[因为没有指定排序元素]在中途更改返回给您的行的顺序[例如,如果添加新数据]意味着您可能会跳过行或其他。

使用hibernate 's ScrollableResults缓慢读取9000万条记录也有一些相关的想法(特别是对于hibernate用户)。

另一种选择是,如果你知道某些列(如"id")大部分是连续的,你可以"按块"迭代(获得最大值,然后在块上进行数字迭代)。或者其他可以"分块"的列

我只是觉得有必要回答这个老帖子。

注意,这是大数据的典型场景,不仅要在多个线程中获取数据,还要在多个线程中进一步处理该数据。这种方法并不总是要求在内存中积累所有的数据,它可以在组和/或滑动窗口中处理,并且只需要积累结果,或者将数据进一步传递(其他永久存储)。

为了并行处理数据,通常对源数据应用分区方案或拆分方案。如果数据是原始文本,则这可能是在中间某处剪切的随机大小。对于数据库,分区方案只不过是在查询上应用了一个额外的where条件,以允许分页。可以是这样的:

  • 驱动程序:将我的数据分成几个部分,并启动4个worker
  • 4 x(工人计划):给我第一部分…4/4的数据

这可以转换成(伪)sql,如:

SELECT ...
FROM (... Subquery ...)
WHERE date = SYSDATE - days(:partition)

最新更新