ruby-on-rails - ActiveRecord:从主键数组加载相应的记录数组(保留顺序、重复、最大化性能)

标签 ruby-on-rails ruby-on-rails-3 activerecord eager-loading

(原:ActiveRecord 中的反向预加载)

我遇到了这个奇怪的问题,我知道我需要使用急切加载,但由于这是一个如此奇怪的用例,所以它的工作效果不太好。

代码:

class Task < ActiveRecord::Base
 belongs_to :project

class Project < ActiveRecord::Base
 has_many :tasks

问题:

我知道,在传统设置中,您有一个项目并想要渲染任务,您可以使用急切加载来加载任务一次,而不是按顺序迭代它们。但是,就我而言,我有一个任务列表,对于每个任务,我需要获取适当的项目。当顺序渲染时,Rails SQL 缓存会有所帮助,但我有很多任务,所以我最终会一遍又一遍地加载同一个项目。

我该怎么做才能避免这种困惑的情况?

编辑:

我正在努力澄清情况。我有多个任务 id 数组。即

type_a_tasks = [1,2,3,1,2,3]
type_b_tasks = [1,2,2,3,3]

请注意,可以有相同的任务。现在我想像在函数式编程中一样映射列表,这样我就可以获得实际的任务及其关联,而不是 ids

type_a_tasks = [Task #1, Task #2, etc.]
type_b_tasks = [Task #1, Task #2, etc.]

我知道我可以通过以下方式获得任务

Task.includes(:project).find(task_a_tasks.concat(task_b_tasks))

但随后我将其简化为任务集并丢失了集合的顺序。是不是更清楚了?

最佳答案

让我们首先从最明显的方法开始:

type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| Task.includes(:project).find(task_id) }
type_b_tasks = type_b_task_ids.map { |task_id| Task.includes(:project).find(task_id) }

上面的内容很简单,可读,但可能:它将为每个不同的task_id以及执行一次数据库往返给定任务中每个不同 project_id 的数据库往返。所有延迟都会增加,因此您希望批量加载任务(以及相应的项目)。

如果你可以让Rails批量加载(预取)并缓存这些相同的记录,例如两次往返(一次用于所有不同的任务,一次用于所有不同的任务),那就太好了关联的项目),然后只需使用与上面完全相同的代码 - 除了 find 始终会命中缓存而不是数据库。

不幸的是,Rails 中的情况并非如此(默认情况下),如 ActiveRecord uses a query cache 。在 Task.find([1,2,3]) 之后运行 Task.find(1)(SELECT * FROMtasks WHERE id=1) > (SELECT * FROMtasks WHERE id IN (1,2,3)) 将不会利用查询缓存,因为第一个查询与第二个查询不同。 (不过,运行 Task.find(1) 第二次、第三次等等利用查询缓存,因为 Rails 会看到完全相同的 SELECT 多次查询飞越并返回缓存的结果集。)

输入IdentityMap缓存。身份映射缓存的不同之处在于它基于每个表和主键缓存记录而不是查询。因此,运行 Task.find([1,2,3]) 将在 Identity Map Cache 中为表 tasks 填写三个记录(ID 为 的条目123),后续的 Task.find(1) 将立即返回表的缓存记录任务和ID 1

# with IdentityMap turned on (see IdentityMap documentation)
# prefetch all distinct tasks and their associated projects
# throw away the result, we only want to prep the cache
Task.includes(:project).find(type_a_task_ids & type_b_task_ids)
# proceed with regular logic
type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| Task.includes(:project).find(task_id) }
type_b_tasks = type_b_task_ids.map { |task_id| Task.includes(:project).find(task_id) }

但是,IdentityMap has never been active by default (for good reason) ,和was ultimately removed from Rails .

如何在没有 IdentityMap 的情况下获得相同的结果?简单:

# prefetch all distinct tasks and their associated projects
# store the result in our own identity cache
my_tasks_identity_map = \
  Hash[Task.includes(:project).find(type_a_task_ids & type_b_task_ids).map { |task|
    [ task.id, task ]
  }]
# proceed with cache-centric logic
type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| my_tasks_identity_map[task_id] }
type_b_tasks = type_b_task_ids.map { |task_id| my_tasks_identity_map[task_id] }

关于ruby-on-rails - ActiveRecord:从主键数组加载相应的记录数组(保留顺序、重复、最大化性能),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13556898/

相关文章:

ruby-on-rails-3 - Rails 3.2.2 简单形式嵌套模型(has_many through)可以工作,但需要改进

ruby-on-rails - ActiveRecord,has_many :through,与STI的多态关联

ruby-on-rails - validates_uniqueness_of 的条件验证消息

javascript - Ruby on Rails/Javascript - 如何判断内容是否动态生成?

ruby-on-rails - Ruby on Rails : :include on a polymorphic association with submodels

ruby - 如何阻止 Savon 向 soap.body 添加前缀

mysql - rails 3 : What is the best way to update a column in a very large table

javascript - Rdio 风格弹出框?

ruby-on-rails - 如何手动删除轮胎索引

ruby-on-rails-3 - 在 ActiveRecord Rails 3.1 的向下迁移方法中截断表