鉴于以下模型:
Room (id, title, suggested)
has_many :room_apps, :dependent => :destroy
RoomApp (room_id, app_id, appable_id, appable_type)
belongs_to :appable, :polymorphic => true
has_many :colors, :as => :appable
has_many :shirts, :as => :appable
Colors (room_id)
belongs_to :room
belongs_to :room_app
belongs_to :app
我想要的是获得所有建议的房间。在我的控制器中,我有:
@suggested_rooms = Room.includes(:room_apps).find_all_by_suggested(true).first(5)
这里的问题是包含不起作用并且数据库被多次命中:
Processing by PagesController#splash as HTML
Room Load (0.6ms) SELECT "rooms".* FROM "rooms" WHERE "rooms"."suggested" = 't' ORDER BY last_activity_at DESC
RoomApp Load (0.6ms) SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND ("room_apps".room_id IN (5,4,3)) ORDER BY created_at DESC
RoomApp Load (5.9ms) SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 6 AND ("room_apps".room_id = 5) ORDER BY created_at DESC LIMIT 1
Color Load (0.4ms) SELECT "colors".* FROM "colors" WHERE "colors"."id" = 5 LIMIT 1
RoomApp Load (0.6ms) SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 5 AND ("room_apps".room_id = 4) ORDER BY created_at DESC LIMIT 1
Color Load (0.4ms) SELECT "colors".* FROM "colors" WHERE "colors"."id" = 4 LIMIT 1
RoomApp Load (0.4ms) SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."published" = 't' AND "room_apps"."id" = 4 AND ("room_apps".room_id = 3) ORDER BY created_at DESC LIMIT 1
Color Load (0.3ms) SELECT "colors".* FROM "colors" WHERE "colors"."id" = 3 LIMIT 1
设置不正确吗?我希望能够获得建议的房间,并使用包含room_apps的一次打击,而不是目前每个房间的打击。
想法?感谢
答案 0 :(得分:0)
我认为你要么像这样使用完整的Rails3 arel接口:
@suggested_rooms = Room.includes(:room_apps).where(:suggested => true).limit(5)
或者为Rails 2.3x执行此操作:
@suggested_rooms = Room.find_all_by_suggested(true, :include=>:room_apps).first(5)
答案 1 :(得分:0)
进行了一些挖掘,我想我知道发生了什么。
默认情况下,include不会生成单个查询。它生成N个查询,其中N是要包含的模型数。
ruby-1.9.2-p180 :014 > Room.where(:suggested => true).includes(:room_apps => :colors)
Room Load (0.5ms) SELECT "rooms".* FROM "rooms" WHERE "rooms"."suggested" = 't'
RoomApp Load (0.8ms) SELECT "room_apps".* FROM "room_apps" WHERE "room_apps"."room_id" IN (1)
Color Load (0.5ms) SELECT "colors".* FROM "colors" WHERE "colors"."room_app_id" IN (1)
一个例外是如果你有一个where子句引用其中一个模型表,在这种情况下它将使用LEFT OUTER JOIN将where子句添加到该表。
如果你想INNER JOIN中有一些模型 AND 包含它们,你必须使用两个连接和包含给定的模型。单独加入只会在关系中进行INNER JOIN,包括将拉入字段并设置返回的模型,其关系完好无损。
ruby-1.9.2-p180 :015 > Room.where(:suggested => true).joins(:room_apps => :colors)
Room Load (0.8ms) SELECT "rooms".*
FROM "rooms"
INNER JOIN "room_apps"
ON "room_apps"."room_id" = "rooms"."id"
INNER JOIN "colors"
ON "colors"."room_app_id" = "room_apps"."id"
WHERE "rooms"."suggested" = 't'
ruby-1.9.2-p180 :016 > Room.where(:suggested => true).joins(:room_apps => :colors).includes(:room_apps => :colors)
SQL (0.6ms) SELECT "rooms"."id" AS t0_r0, "rooms"."suggested" AS t0_r1, "rooms"."created_at" AS t0_r2, "rooms"."updated_at" AS t0_r3, "room_apps"."id" AS t1_r0, "room_apps"."room_id" AS t1_r1, "room_apps"."created_at" AS t1_r2, "room_apps"."updated_at" AS t1_r3, "colors"."id" AS t2_r0, "colors"."room_id" AS t2_r1, "colors"."room_app_id" AS t2_r2, "colors"."created_at" AS t2_r3, "colors"."updated_at" AS t2_r4
FROM "rooms"
INNER JOIN "room_apps"
ON "room_apps"."room_id" = "rooms"."id"
INNER JOIN "colors"
ON "colors"."room_app_id" = "room_apps"."id"
WHERE "rooms"."suggested" = 't'
最后一个查询中令人费解的SELECT部分是ARel,确保所有模型中的字段都是唯一的,并且能够在需要映射回实际模型时进行区分。
您是单独使用包含还是包含连接是一个问题,即您带回的数据量,以及如果您不进行INNER JOIN可能会有多大的速度差异,从而导致返回大量重复数据。我想如果'房间'有十几个字段,'颜色'有1个字段,但有100种颜色映射到单个房间,而不是总共拉回113个字段(1个房间* 13 + 100种颜色) * 1)最终会有1400个字段(13 + 1 * 100种颜色)。不完全是性能提升。
虽然单独使用include的缺点是,如果每个房间确实有大量的颜色,IN(ID)将是巨大的,是一把双刃剑。
这是我使用sqlite3
进行各种配置的快速测试我设置了两套房间,其中一套房间:建议=>是的,另一个:建议=>假。建议的房间在房间/房间/应用/颜色之间的比例为1:1:2,建议的假房间设置为1:1:10比例相同,建议与未建议的比例为10:1。
# 100/10 rooms
# insert only
100 * 1/1/2: 8.1ms
10 * 1/1/10: 3.2ms
# insert + joins
100 * 1/1/2: 6.2ms
10 * 1/1/10: 3.1ms
# 1000/100 rooms
# insert only
1000 * 1/1/2: 76.8ms
100 * 1/1/10: 19.8ms
# insert + joins
1000 * 1/1/2: 54.5ms
100 * 1/1/10: 23.1ms
时间本身并不相关,这是通过IRB在UXntu客户机上运行在WinXP主机上的硬盘上。鉴于你有一个限制(5),它可能不会产生巨大的差异。