跳至內容跳至搜尋

Active Record 批次

命名空間
方法
F
I

常數

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 方法)效率不佳,因為會嘗試一次實體化所有物件。

針對這類案例,批次處理方法允許您以批次處理記錄,大幅減少記憶體使用量。

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

尊重限制條件,若有指定限制條件,則不需設定批次大小:可以小於、等於或大於限制條件。

如果有多個工作者處理相同的處理佇列,startfinish 會特別有用。您可以讓工作者 1 處理 id 1 到 9999 之間的所有記錄,讓工作者 2 處理 10000 之後的所有記錄,方式是在每個工作者設定 :start:finish 選項。

# 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 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 提供區塊,它會傳回一個 Enumerator,以與其他方法串連

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

尊重限制條件,若有指定限制條件,則不需設定批次大小:可以小於、等於或大於限制條件。

如果有多個工作者處理相同的處理佇列,startfinish 會特別有用。您可以讓工作者 1 處理 id 1 到 9999 之間的所有記錄,讓工作者 2 處理 10000 之後的所有記錄,方式是在每個工作者設定 :start:finish 選項。

# 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 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 (僅記錄的一小部分已封存),選擇這種方法是有意義的。

會遵循限制,而且如果沒有限制,批次大小無需設定,它可以小於、等於或大於限制。

如果有多個工作者處理相同的處理佇列,startfinish 會特別有用。您可以讓工作者 1 處理 id 1 到 9999 之間的所有記錄,讓工作者 2 處理 10000 之後的所有記錄,方式是在每個工作者設定 :start:finish 選項。

# 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 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