【问题标题】:Active Record in_batches vs find_in_batchesActive Record in_batches 与 find_in_batches
【发布时间】:2019-10-21 11:49:09
【问题描述】:

我想对数据库中的数百万条记录执行批处理操作。

根据 ActiveRecord 文档,有两种方法可以执行批处理操作,即#find_in_batches & #in_batches。但是,除了一个返回 Enumerator 而另一个返回 ActiveRecord Relation 之外,我似乎找不到它们之间的任何区别。

因此,考虑到它们具有不同的性能,我想知道在哪种情况下哪个性能更好。而且,除了原始 SQL 之外,还有没有更好的方法来有条件地更新数百万行?

【问题讨论】:

  • 使用.update_all 并一次更新所有行总是比将记录从数据库中拉出并逐个更新一个数量级要好。如果可能的话,这绝对是您的首选。
  • 我首先要问,如果一个总是比另一个更好,为什么他们会公开这两种方法(仅仅因为一个调用另一个并不意味着它的性能较低,它只取决于用例) .正如@max 所说,update_all 是首选。如果您遇到一些问题(超时等),那么跨多个工人的批次可能是值得关注的。
  • #in_batches 是一个较低级别的函数,可用于构建#find_in_batches 之类的东西无论如何,这就像将两个马品种与太空船进行比较。不要害怕一点原始的sql。它不会自动邪恶。
  • “因为要更新的值是基于行的动态”——这听起来像是实际上可以用 SQL 完成的事情。您可以使用子查询、函数、横向连接等。
  • 取决于数据库,但在 postgres 上,您可以使用外部数据包装器 (FDW) 连接驻留在另一个数据库上的表,就好像它在同一个数据库上一样。

标签: ruby-on-rails activerecord


【解决方案1】:

您必须查看源代码才能了解此处的性能差异 -

def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
  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) do
      total = apply_limits(relation, start, finish).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) do |batch|
    yield batch.to_a
  end
end

注意in_batches 方法是如何被调用的,结果是如何存储在一个数组中的。这将占用更多内存。 in_batches 因此在这里是一种更有效的方式。

【讨论】:

  • 没有任何上下文我同意。但是,仅仅因为一个调用另一个并不意味着它的性能较低......它仅取决于当前未知的用例。
【解决方案2】:

简而言之,find_in_batches 产生每批找到的记录,而in_batches 产生ActiveRecord::Relation 对象。

所以下面的代码:

Post.find_in_batches do |group|
   group.each { |post| puts post.title }
end

每个批次只会向数据库发送一个查询以检索该批次的所有帖子数据:

SELECT "posts".* FROM "posts" WHERE ...

但是:

Post.in_batches do |group|
   group.each { |post| puts post.title }
end

将每批发送两个查询到数据库。获取该批次帖子 ID 的第一个查询:

SELECT "posts"."id" FROM "posts" WHERE ...

第二个查询获取该批次的所有帖子数据:

SELECT "posts".* FROM "posts" WHERE ...

更多细节:

如果您查看here 这两个函数的源代码,您会看到find_in_batches 实际上调用in_batches 并在参数中传递了load: true。但是load 的默认值是false in in_batches

如果您进一步查看in_batches 中使用load 值的部分,它将如下所示:

        if load
          records = batch_relation.records
          ids = records.map(&:id)
          yielded_relation = where(primary_key => ids)
          yielded_relation.load_records(records)
        else
          ids = batch_relation.pluck(primary_key)
          yielded_relation = where(primary_key => ids)
        end

原文解释可见:https://www.codehub.com.vn/Difference-between-find_in_batches-vs-in_batches-in-Ruby-on-Rails

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-14
    • 2013-01-09
    • 2012-10-09
    • 2011-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多