(原: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 为 的条目1
、2
和 3
),后续的 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/