Active Record 批处理
常量
DEFAULT_ORDER | = | :asc |
ORDER_IGNORE_MESSAGE | = | "范围排序被忽略,强制使用批处理排序." |
实例公共方法
find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block) 链接
循环遍历数据库中的一组记录(例如,使用 Scoping::Named::ClassMethods.all
方法)非常低效,因为它会尝试一次性实例化所有对象。
在这种情况下,批处理方法允许您分批处理记录,从而大大减少内存消耗。
find_each
方法使用 find_in_batches
,批次大小为 1000(或由 :batch_size
选项指定)。
Person.find_each do |person|
person.do_awesome_stuff
end
Person.where("age > 21").find_each do |person|
person.party_all_night!
end
如果您没有向 find_each
提供代码块,它将返回一个枚举器,用于与其他方法链接。
Person.find_each.with_index do |person, index|
person.award_trophy(index + 1)
end
选项
-
:batch_size
- 指定批次大小。默认为 1000。 -
:start
- 指定要开始的主键值,包含该值。 -
:finish
- 指定要结束的主键值,包含该值。 -
:error_on_ignore
- 覆盖应用程序配置,指定当关系中存在排序时是否应引发错误。 -
:order
- 指定主键排序(可以是:asc
或:desc
或包含 :asc 或 :desc 的数组)。默认为:asc
。class Order < ActiveRecord::Base self.primary_key = [:id_1, :id_2] end Order.find_each(order: [:asc, :desc])
在上面的代码中,
id_1
按升序排序,id_2
按降序排序。
限制会被遵守,如果存在限制,则没有对批次大小的要求:它可以小于、等于或大于限制。
start
和 finish
选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :start
和 :finish
选项,让工作者 1 处理 id 1 到 9999 之间的所有记录,而工作者 2 处理从 10000 开始的所有记录。
# In worker 1, let's process until 9999 records.
Person.find_each(finish: 9_999) do |person|
person.party_all_night!
end
# In worker 2, let's process from record 10_000 and onwards.
Person.find_each(start: 10_000) do |person|
person.party_all_night!
end
注意:顺序可以是升序 (:asc) 或降序 (:desc)。它会自动设置为主键的升序(“id ASC”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。
注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 79 def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block) if block_given? find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records| records.each(&block) end else enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do relation = self apply_limits(relation, start, finish, build_batch_orders(order)).size end end end
find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER) 链接
将通过查找选项找到的每批记录作为数组生成。
Person.where("age > 21").find_in_batches do |group|
sleep(50) # Make sure it doesn't get too crowded in there!
group.each { |person| person.party_all_night! }
end
如果您没有为 find_in_batches
提供代码块,它将返回一个枚举器,用于与其他方法进行链接。
Person.find_in_batches.with_index do |group, batch|
puts "Processing group ##{batch}"
group.each(&:recover_from_last_night!)
end
要逐个生成每条记录,请使用 find_each
。
选项
-
:batch_size
- 指定批次大小。默认为 1000。 -
:start
- 指定要开始的主键值,包含该值。 -
:finish
- 指定要结束的主键值,包含该值。 -
:error_on_ignore
- 覆盖应用程序配置,指定当关系中存在排序时是否应引发错误。 -
:order
- 指定主键排序(可以是:asc
或:desc
或包含 :asc 或 :desc 的数组)。默认为:asc
。class Order < ActiveRecord::Base self.primary_key = [:id_1, :id_2] end Order.find_in_batches(order: [:asc, :desc])
在上面的代码中,
id_1
按升序排序,id_2
按降序排序。
限制会被遵守,如果存在限制,则没有对批次大小的要求:它可以小于、等于或大于限制。
start
和 finish
选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :start
和 :finish
选项,让工作者 1 处理 id 1 到 9999 之间的所有记录,而工作者 2 处理从 10000 开始的所有记录。
# Let's process from record 10_000 on.
Person.find_in_batches(start: 10_000) do |group|
group.each { |person| person.party_all_night! }
end
注意:顺序可以是升序 (:asc) 或降序 (:desc)。它会自动设置为主键的升序(“id ASC”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。
注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 148 def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER) relation = self unless block_given? return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do total = apply_limits(relation, start, finish, build_batch_orders(order)).size (total - 1).div(batch_size) + 1 end end in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch| yield batch.to_a end end
in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block) 链接
生成 ActiveRecord::Relation
对象,用于处理一批记录。
Person.where("age > 21").in_batches do |relation|
relation.delete_all
sleep(10) # Throttle the delete queries
end
如果您没有为 in_batches
提供代码块,它将返回一个 BatchEnumerator
,它是可枚举的。
Person.in_batches.each_with_index do |relation, batch_index|
puts "Processing relation ##{batch_index}"
relation.delete_all
end
在返回的 BatchEnumerator
对象上调用方法的示例
Person.in_batches.delete_all
Person.in_batches.update_all(awesome: true)
Person.in_batches.each_record(&:party_all_night!)
选项
-
:of
- 指定批次的大小。默认为 1000。 -
:load
- 指定是否应该加载关系。默认为 false。 -
:start
- 指定要开始的主键值,包含该值。 -
:finish
- 指定要结束的主键值,包含该值。 -
:error_on_ignore
- 覆盖应用程序配置,指定当关系中存在排序时是否应引发错误。 -
:order
- 指定主键排序(可以是:asc
或:desc
或包含 :asc 或 :desc 的数组)。默认为:asc
。class Order < ActiveRecord::Base self.primary_key = [:id_1, :id_2] end Order.in_batches(order: [:asc, :desc])
在上面的代码中,
id_1
按升序排序,id_2
按降序排序。 -
:use_ranges
- 指定是否使用范围迭代(id >= x 且 id <= y)。这可以使对整个表或几乎整个表的多次迭代速度更快。默认情况下,只有整个表的迭代使用这种迭代方式。您可以通过传递false
来禁用此行为。如果您迭代表,并且唯一条件是,例如,archived_at: nil
(并且只有极少数记录被归档),那么选择这种方法是有意义的。
限制会被尊重,如果存在限制,则对批次大小没有要求,它可以小于、等于或大于限制。
start
和 finish
选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :start
和 :finish
选项,让工作者 1 处理 id 1 到 9999 之间的所有记录,而工作者 2 处理从 10000 开始的所有记录。
# Let's process from record 10_000 on.
Person.in_batches(start: 10_000).update_all(awesome: true)
在关系上调用 where 查询方法的示例
Person.in_batches.each do |relation|
relation.update_all('age = age + 1')
relation.where('age > 21').update_all(should_party: true)
relation.where('age <= 21').delete_all
end
注意:如果您要遍历每个记录,您应该在生成的 BatchEnumerator 上调用 each_record
Person.in_batches.each_record(&:party_all_night!)
注意:顺序可以是升序 (:asc) 或降序 (:desc)。它会自动设置为主键的升序(“id ASC”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。
注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 239 def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block) unless Array(order).all? { |ord| [:asc, :desc].include?(ord) } raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}" end unless block return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges) end if arel.orders.present? act_on_ignored_order(error_on_ignore) end batch_limit = of if limit_value remaining = limit_value batch_limit = remaining if remaining < batch_limit end if self.loaded? batch_on_loaded_relation( relation: self, start: start, finish: finish, order: order, batch_limit: batch_limit, &block ) else batch_on_unloaded_relation( relation: self, start: start, finish: finish, load: load, order: order, use_ranges: use_ranges, remaining: remaining, batch_limit: batch_limit, &block ) end end