跳至内容 跳至搜索

Active Record 批处理

命名空间
方法
F
I

常量

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 按降序排序。

限制会被遵守,如果存在限制,则没有对批次大小的要求:它可以小于、等于或大于限制。

startfinish 选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :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”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。

注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。

# 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 按降序排序。

限制会被遵守,如果存在限制,则没有对批次大小的要求:它可以小于、等于或大于限制。

startfinish 选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :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”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。

注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。

# 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(并且只有极少数记录被归档),那么选择这种方法是有意义的。

限制会被尊重,如果存在限制,则对批次大小没有要求,它可以小于、等于或大于限制。

startfinish 选项在您希望多个工作者处理同一个处理队列时特别有用。您可以通过在每个工作者上设置 :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”)。这也意味着此方法仅在主键可排序时(例如整数或字符串)有效。

注意:本质上,如果其他进程正在修改数据库,批处理会受到竞争条件的影响。

# 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