我无法理解什么时候充满热情,什么时候不充分。我会做一个实际的例子: 拿下下图中的模型,我必须让那些已经打过一定比赛的球队(所以我需要一些联赛):
在我的 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
答案 0 :(得分:1)
这里有很多事情需要注意。
首先,您可能不需要同时使用joins
和includes
。它们相似但不一样。
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}}}])
检查日志以确保查询和预先加载按预期工作(包括性能)。