Rails4:急切加载无法正常工作

时间:2013-12-29 14:52:47

标签: ruby-on-rails

我无法理解什么时候充满热情,什么时候不充分。我会做一个实际的例子: 拿下下图中的模型,我必须让那些已经打过一定比赛的球队(所以我需要一些联赛):

在我的 MatchesController 中,我执行以下操作:

@matches = Match.scheduled_or_playing
.joins(match_locations: {scores: {team_match: :team}})
.includes(match_locations: {scores: {team_match: :team}})

并在我的索引视图中执行以下操作:

<% @matches.each do |m| %>
    <%= m.teams.map(&:name).join " | "  %> 
<% end

这会触发大量查询,如此日志所示:

  SQL (5.1ms)  SELECT "matches"."id" AS t0_r0, "matches"."server_id" AS t0_r1, "matches"."pool_id" AS t0_r2, "matches"."state" AS t0_r3, "matches"."can_draw" AS t0_r4, "matches"."scheduled_at" AS t0_r5, "matches"."created_at" AS t0_r6, "matches"."updated_at" AS t0_r7, "match_locations"."id" AS t1_r0, "match_locations"."match_id" AS t1_r1, "match_locations"."location_id" AS t1_r2, "match_locations"."created_at" AS t1_r3, "match_locations"."updated_at" AS t1_r4, "scores"."id" AS t2_r0, "scores"."match_location_id" AS t2_r1, "scores"."team_match_id" AS t2_r2, "scores"."points" AS t2_r3, "scores"."created_at" AS t2_r4, "scores"."updated_at" AS t2_r5, "team_matches"."id" AS t3_r0, "team_matches"."team_id" AS t3_r1, "team_matches"."created_at" AS t3_r2, "team_matches"."updated_at" AS t3_r3, "teams"."id" AS t4_r0, "teams"."encrypted_password" AS t4_r1, "teams"."remember_created_at" AS t4_r2, "teams"."sign_in_count" AS t4_r3, "teams"."current_sign_in_at" AS t4_r4, "teams"."last_sign_in_at" AS t4_r5, "teams"."current_sign_in_ip" AS t4_r6, "teams"."last_sign_in_ip" AS t4_r7, "teams"."ts_address" AS t4_r8, "teams"."ts_password" AS t4_r9, "teams"."picture_url" AS t4_r10, "teams"."name" AS t4_r11, "teams"."username" AS t4_r12, "teams"."created_at" AS t4_r13, "teams"."updated_at" AS t4_r14 FROM "matches" INNER JOIN "match_locations" ON "match_locations"."match_id" = "matches"."id" INNER JOIN "scores" ON "scores"."match_location_id" = "match_locations"."id" INNER JOIN "team_matches" ON "team_matches"."id" = "scores"."team_match_id" INNER JOIN "teams" ON "teams"."id" = "team_matches"."team_id" WHERE "matches"."state" IN (1, 2) ORDER BY matches.scheduled_at ASC
  Team Load (1.1ms)  SELECT "teams".* FROM "teams" INNER JOIN "team_matches" ON "teams"."id" = "team_matches"."team_id" INNER JOIN "scores" ON "team_matches"."id" = "scores"."team_match_id" INNER JOIN "match_locations" ON "scores"."match_location_id" = "match_locations"."id" WHERE "match_locations"."match_id" = $1  [["match_id", 56]]
  Team Load (1.1ms)  SELECT "teams".* FROM "teams" INNER JOIN "team_matches" ON "teams"."id" = "team_matches"."team_id" INNER JOIN "scores" ON "team_matches"."id" = "scores"."team_match_id" INNER JOIN "match_locations" ON "scores"."match_location_id" = "match_locations"."id" WHERE "match_locations"."match_id" = $1  [["match_id", 68]]

  ....(n times)

  Team Load (1.5ms)  SELECT "teams".* FROM "teams" INNER JOIN "team_matches" ON "teams"."id" = "team_matches"."team_id" INNER JOIN "scores" ON "team_matches"."id" = "scores"."team_match_id" INNER JOIN "match_locations" ON "scores"."match_location_id" = "match_locations"."id" WHERE "match_locations"."match_id" = $1  [["match_id", 47]]

但是,如果我以这种方式修改视图 ......

<% @matches.each do |m| %>
    <%= m.teams  %> 
<% end %>

视图不会产生相同的结果,但它应该加载相同的类和attrs。 然而,奇迹般地,现在急切的加载工作,这里是日志

 SQL (4.9ms)  SELECT "matches"."id" AS t0_r0, "matches"."server_id" AS t0_r1, "matches"."pool_id" AS t0_r2, "matches"."state" AS t0_r3, "matches"."can_draw" AS t0_r4, "matches"."scheduled_at" AS t0_r5, "matches"."created_at" AS t0_r6, "matches"."updated_at" AS t0_r7, "match_locations"."id" AS t1_r0, "match_locations"."match_id" AS t1_r1, "match_locations"."location_id" AS t1_r2, "match_locations"."created_at" AS t1_r3, "match_locations"."updated_at" AS t1_r4, "scores"."id" AS t2_r0, "scores"."match_location_id" AS t2_r1, "scores"."team_match_id" AS t2_r2, "scores"."points" AS t2_r3, "scores"."created_at" AS t2_r4, "scores"."updated_at" AS t2_r5, "team_matches"."id" AS t3_r0, "team_matches"."team_id" AS t3_r1, "team_matches"."created_at" AS t3_r2, "team_matches"."updated_at" AS t3_r3, "teams"."id" AS t4_r0, "teams"."encrypted_password" AS t4_r1, "teams"."remember_created_at" AS t4_r2, "teams"."sign_in_count" AS t4_r3, "teams"."current_sign_in_at" AS t4_r4, "teams"."last_sign_in_at" AS t4_r5, "teams"."current_sign_in_ip" AS t4_r6, "teams"."last_sign_in_ip" AS t4_r7, "teams"."ts_address" AS t4_r8, "teams"."ts_password" AS t4_r9, "teams"."picture_url" AS t4_r10, "teams"."name" AS t4_r11, "teams"."username" AS t4_r12, "teams"."created_at" AS t4_r13, "teams"."updated_at" AS t4_r14 FROM "matches" INNER JOIN "match_locations" ON "match_locations"."match_id" = "matches"."id" INNER JOIN "scores" ON "scores"."match_location_id" = "match_locations"."id" INNER JOIN "team_matches" ON "team_matches"."id" = "scores"."team_match_id" INNER JOIN "teams" ON "teams"."id" = "team_matches"."team_id" WHERE "matches"."state" IN (1, 2) ORDER BY matches.scheduled_at ASC

我真的不明白为什么

编辑:

根据要求,我在这里添加模型:

团队

class Team < ActiveRecord::Base  
  has_many :team_matches
  has_many :matches, :through => :team_matches
end

TeamMatch

class TeamMatch < ActiveRecord::Base
  belongs_to :team
  has_many :scores, :dependent => :destroy, :inverse_of => :team_match
  has_many :match_locations, through: :scores
end

得分

class Score < ActiveRecord::Base
  belongs_to :team_match
  belongs_to :match_location
end

MatchLocation

class MatchLocation < ActiveRecord::Base
  belongs_to :location
  belongs_to :match      
  has_many :scores
  accepts_nested_attributes_for :scores
  has_many :team_matches, through: :scores
end

匹配

class Match < ActiveRecord::Base  
  has_many :match_locations
  accepts_nested_attributes_for :match_locations, allow_destroy: true 
  has_many :scores, through: :match_locations
  has_many :team_matches, through: :scores
  has_many :teams, through: :team_matches
end

models relations

2 个答案:

答案 0 :(得分:1)

这里有很多事情需要注意。

首先,您可能不需要同时使用joinsincludes。它们相似但不一样。

includes执行急切提取,而joins则不提取。请注意,在您的情况下,joins之前有includes。因此,使用join获取关联。

接下来,查看您的不同观点,

当您执行m.teams时,您只是指关联,而不是实际的teams集合。但是,在您执行m.teams.map...时的第一个视图中,您现在引用了关联的属性,这会导致ActiveRecord根据需要获取Team个对象(即延迟)。因此N + 1查询问题。

Ryan Bates有一个很棒的Rails Cast to compare this

答案 1 :(得分:1)

我认为ActiveRecord没有通过嵌套的关联来识别你想要加载teams的方式。尝试在“匹配级别”中包含teams而不是嵌套。

@matches = Match.scheduled_or_playing
  .joins(:teams)
  .includes(:teams)

如果你需要所有其他嵌套的预先加载,那么只需将其重新包含在:

@matches = Match.scheduled_or_playing
  .joins([:teams, {match_locations: {scores: {team_match: :team}}}])
  .includes([:teams, {match_locations: {scores: {team_match: :team}}}])

检查日志以确保查询和预先加载按预期工作(包括性能)。