为什么在处理多对多关系时,对于 ActiveRecord 关联,“<<”比“+=”快得多?

Why is "<<" so much faster than "+=" for an ActiveRecord association when dealing with a many-to-many relationship?

我们有一个工作区 table 和一个工作区组 table,这两个 table 之间的多对多关系通过一个名为 WorkspaceGroupAssociation 的连接 table (工作区就像我们领域模型中的项目)。所以一个项目可以属于多个组,一个组可以有多个项目。

我们有一些团队有数千个项目,在我们的可观察性工具中,我们最近注意到以下旧代码非常慢(注意以下代码是该方法的简化版本):

class WorkspaceGroup < ApplicationRecord
  def add_workspaces(workspace_ids)
    self.workspace_ids |= workspace_ids
  end
end

我们有一个组已经有大约 5,000 个工作区,添加这些新的工作区 ID 花费了 2 分钟以上。

我们最初的方法是将 self.workspace_ids |= workspace_ids 更改为 self.workspace_ids += workspace_ids,但这在性能方面根本没有改变。然后我们尝试了以下方法,效果很好:

  def add_workspaces(workspace_ids)
    existing_workspaces = self.workspaces
    workspaces_to_add = Workspace.where(id: workspace_ids) - existing_workspaces
    workspaces_to_add.each do |workspace|
      self.workspaces << workspace
    end
  end

上述代码的作者说,性能提升是因为我们没有在新代码中实例化 5,000 个 Workspace 模型的新实例,而是在旧代码中。

我很好奇为什么旧代码是这样,而新代码却不是。为什么 self.workspace_ids += 会导致实例化数千个新的 ActiveRecord 实例,而 self.workspaces << 却不会?

+ 为 Rails

中的集合执行此操作
def +(other)
  Collection.new(to_a + other.to_a)
end

虽然 << 这样做...

def <<(*records)
  proxy_association.concat(records) && self
end

我的猜测是,创建一个新的 Collection 是一个比串联更昂贵的操作。


https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-3C-3C

https://api.rubyonrails.org/classes/Rails/Initializable/Collection.html#method-i-2B