Active Record 批量操作
常量
DEFAULT_ORDER | = | :asc |
ORDER_IGNORE_MESSAGE | = | "作用域排序被忽略,请使用 :cursor 和 :order 配置自定义排序。" |
实例公有方法
find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block) 链接
循环遍历数据库中的一组记录(例如,使用 Scoping::Named::ClassMethods.all
方法)效率非常低,因为它会尝试一次性实例化所有对象。
在这种情况下,批量处理方法允许您以批次的方式处理记录,从而极大地减少内存消耗。
The 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
- 覆盖应用程序配置以指定在关联中存在排序时是否应引发错误。 -
:cursor
- 指定用于批处理的列(可以是列名或列名数组)。默认为主键。 -
: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”)。这也意味着此方法仅在游标列可排序时才有效(例如,整数或字符串)。
注意:在使用自定义列进行批处理时,它们应该包含至少一个唯一列(例如主键)作为 tiebreaker。此外,为了减少出现竞争条件的可能性,所有列都应该是静态的(在设置后不可更改)。
注意:根据其本质,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 85 def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block) if block_given? find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, 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, cursor: cursor, order: order) do relation = self cursor = Array(cursor) apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size end end end
find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, 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
- 覆盖应用程序配置以指定在关联中存在排序时是否应引发错误。 -
:cursor
- 指定用于批处理的列(可以是列名或列名数组)。默认为主键。 -
: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”)。这也意味着此方法仅在游标列可排序时才有效(例如,整数或字符串)。
注意:在使用自定义列进行批处理时,它们应该包含至少一个唯一列(例如主键)作为 tiebreaker。此外,为了减少出现竞争条件的可能性,所有列都应该是静态的(在设置后不可更改)。
注意:根据其本质,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 161 def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, 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, cursor: cursor, order: order) do cursor = Array(cursor) total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, 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, cursor: cursor, order: order) do |batch| yield batch.to_a end end
in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, 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
- 覆盖应用程序配置以指定在关联中存在排序时是否应引发错误。 -
:cursor
- 指定用于批处理的列(可以是列名或列名数组)。默认为主键。 -
: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 AND 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”)。这也意味着此方法仅在游标列可排序时才有效(例如,整数或字符串)。
注意:在使用自定义列进行批处理时,它们应该包含至少一个唯一列(例如主键)作为 tiebreaker。此外,为了减少出现竞争条件的可能性,所有列都应该是静态的(在设置后不可更改)。
注意:根据其本质,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation/batches.rb, line 259 def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block) cursor = Array(cursor).map(&:to_s) ensure_valid_options_for_batching!(cursor, start, finish, order) if arel.orders.present? act_on_ignored_order(error_on_ignore) end unless block return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges) 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, cursor: cursor, order: order, batch_limit: batch_limit, &block ) else batch_on_unloaded_relation( relation: self, start: start, finish: finish, load: load, cursor: cursor, order: order, use_ranges: use_ranges, remaining: remaining, batch_limit: batch_limit, &block ) end end