看来“多线程从全表读取”有多种方式。
第零方式:如果您的问题只是“我用完 RAM 将整个表读入内存”,那么您可以尝试以某种方式一次处理一行(或一批行),然后处理下一批等. 从而避免将整个表加载到内存中(但仍然是单线程,因此可能很慢)。
第一种方式:让一个线程查询整个表,将单个行放入一个队列中,该队列为多个工作线程提供数据尽可能]。缺点:一次只有一个线程在查询初始数据库,这可能不会“最大化”您的数据库本身。优点:您没有重新运行查询,因此排序顺序不应在中途改变(例如,如果您的查询是 select * from table_name,则返回顺序有些随机,但如果您从同一个返回结果集/查询,你不会得到重复的)。你不会有意外的重复或类似的东西。这是 tutorial 这样做的。
第二种方式:分页,基本上每个线程都知道它应该选择哪个块(在这个例子中是XXX),所以它知道“我应该像select * from table_name order by something start with XXX limit 10一样查询表”。然后每个线程基本上一次处理(在这种情况下)10 [XXX 是线程之间由调用线程递增的共享变量]。
问题在于“按某事排序”,这意味着对于每个查询,数据库都必须对整个表进行排序,这可能会也可能不会,而且成本可能很高,尤其是在表末尾附近。如果它被索引,这应该不是问题。这里需要注意的是,如果数据中存在“空白”,您将执行一些无用的查询,但它们可能仍然很快。例如,如果您有一个 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.02”等。 (灵感来自维基百科如何获得“随机”页面——在插入时为每一行分配一个随机浮点数)。
您真正要避免的事情是在中途对排序顺序进行某种更改。例如,如果您没有指定排序顺序,而只是像这样从多个线程查询select * from table_name start by XXX limit 10,那么可以想象数据库将 [因为没有指定排序元素] 更改它返回给您行的顺序 中途 [例如,如果添加了新数据] 意味着您可以跳过行或不跳过。
Using Hibernate's ScrollableResults to slowly read 90 million records也有一些相关的想法(尤其是针对hibernate用户)。
另一种选择是,如果您知道某个列(例如“id”)大部分是连续的,则可以“按块”迭代该列(获取最大值,然后对块进行数字迭代)。或者其他一些“可分块”的列。