gpt4 book ai didi

ruby-on-rails - activerecord has_many :through find with one sql call

转载 作者:行者123 更新时间:2023-12-04 22:52:54 26 4
gpt4 key购买 nike

我有这 3 个模型:

class User < ActiveRecord::Base
has_many :permissions, :dependent => :destroy
has_many :roles, :through => :permissions
end

class Permission < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
class Role < ActiveRecord::Base
has_many :permissions, :dependent => :destroy
has_many :users, :through => :permissions
end

我想在一个 sql 语句中找到一个用户和它的角色,但我似乎无法做到这一点:

以下声明:
user = User.find_by_id(x, :include => :roles)

给我以下查询:
  User Load (1.2ms)   SELECT * FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
Permission Load (0.8ms) SELECT `permissions`.* FROM `permissions` WHERE (`permissions`.user_id = 1)
Role Load (0.8ms) SELECT * FROM `roles` WHERE (`roles`.`id` IN (2,1))

不完全理想。我如何做到这一点,以便它使用连接执行一个 sql 查询并将用户的角色加载到内存中,这样说:
user.roles

不发出新的 sql 查询

最佳答案

正如 Damien 指出的那样,如果你真的每次都想要一个查询,你应该使用 join。

但是您可能不需要单个 SQL 调用。这就是原因(来自 here ):

优化的急切加载

让我们来看看这个:

Post.find(:all, :include => [:comments])

在 Rails 2.0 之前,我们会在日志中看到类似于以下 SQL 查询的内容:
SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `comments`.`id` AS t1_r0, `comments`.`body` AS t1_r1 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id 

但是现在,在 Rails 2.1 中,相同的命令将提供不同的 SQL 查询。实际上至少是 2 个,而不是 1 个。“这怎么可能是一种改进?”我们来看看生成的 SQL 查询:
SELECT `posts`.`id`, `posts`.`title`, `posts`.`body` FROM `posts` 

SELECT `comments`.`id`, `comments`.`body` FROM `comments` WHERE (`comments`.post_id IN (130049073,226779025,269986261,921194568,972244995))

实现 Eager Loading 的 :include 关键字是为了解决可怕的 1+N 问题。当您有关联时会发生此问题,然后您加载父对象并开始一次加载一个关联,因此出现 1+N 问题。如果您的父对象有 100 个子对象,您将运行 101 个查询,这并不好。尝试优化这一点的一种方法是在 SQL 中使用 OUTER JOIN 子句连接所有内容,这样在单个查询中同时加载父对象和子对象。

看起来是个好主意,实际上仍然如此。但是在某些情况下,怪物外连接变得比许多较小的查询慢。很多人都在讨论,你可以看看门票9640、9497、9560、L109的详细信息。

底线是 :通常将怪物连接拆分为较小的连接似乎更好,如您在上面的示例中所见。这避免了笛卡尔积过载问题。对于初学者,让我们运行查询的外连接版本:
mysql> SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `comments`.`id` AS t1_r0, `comments`.`body` AS t1_r1 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id ;

+-----------+-----------------+--------+-----------+---------+
| t0_r0 | t0_r1 | t0_r2 | t1_r0 | t1_r1 |
+-----------+-----------------+--------+-----------+---------+
| 130049073 | Hello RailsConf | MyText | NULL | NULL |
| 226779025 | Hello Brazil | MyText | 816076421 | MyText5 |
| 269986261 | Hello World | MyText | 61594165 | MyText3 |
| 269986261 | Hello World | MyText | 734198955 | MyText1 |
| 269986261 | Hello World | MyText | 765025994 | MyText4 |
| 269986261 | Hello World | MyText | 777406191 | MyText2 |
| 921194568 | Rails 2.1 | NULL | NULL | NULL |
| 972244995 | AkitaOnRails | NULL | NULL | NULL |
+-----------+-----------------+--------+-----------+---------+
8 rows in set (0.00 sec)

请注意这一点:您是否看到前 3 列(t0_r0 到 t0_r2)中有很多重复?这些是 Post 模型列,其余的是每个帖子的评论列。请注意,“Hello World”帖子重复了 4 次。这就是连接的作用:父行为每个 child 重复。该特定帖子有 4 条评论,因此重复了 4 次。

问题是这对 Rails 造成了沉重打击,因为它必须处理几个小而短暂的对象。在 Rails 方面感受到了痛苦,而在 MySQL 方面则没有那么多。现在,将其与较小的查询进行比较:
mysql> SELECT `posts`.`id`, `posts`.`title`, `posts`.`body` FROM `posts` ;
+-----------+-----------------+--------+
| id | title | body |
+-----------+-----------------+--------+
| 130049073 | Hello RailsConf | MyText |
| 226779025 | Hello Brazil | MyText |
| 269986261 | Hello World | MyText |
| 921194568 | Rails 2.1 | NULL |
| 972244995 | AkitaOnRails | NULL |
+-----------+-----------------+--------+
5 rows in set (0.00 sec)

mysql> SELECT `comments`.`id`, `comments`.`body` FROM `comments` WHERE (`comments`.post_id IN (130049073,226779025,269986261,921194568,972244995));
+-----------+---------+
| id | body |
+-----------+---------+
| 61594165 | MyText3 |
| 734198955 | MyText1 |
| 765025994 | MyText4 |
| 777406191 | MyText2 |
| 816076421 | MyText5 |
+-----------+---------+
5 rows in set (0.00 sec)

其实我有点作弊,我手动删除了上述所有查询中的 created_at 和 updated_at 字段,以便您更清楚地理解它。所以,你有它:帖子结果集,分开且不重复,评论结果集与以前的大小相同。结果集越长、越复杂,这就越重要,因为 Rails 需要处理的对象越多。分配和释放数百或数千个小的重复对象从来都不是一件好事。

但是这个新功能很聪明。假设你想要这样的东西:
>> Post.find(:all, :include => [:comments], :conditions => ["comments.created_at > ?", 1.week.ago.to_s(:db)])

在 Rails 2.1 中,它会理解“comments”表有一个过滤条件,所以它不会把它分解成小的查询,而是会生成旧的外连接版本,如下所示:
SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `posts`.`created_at` AS t0_r3, `posts`.`updated_at` AS t0_r4, `comments`.`id` AS t1_r0, `comments`.`post_id` AS t1_r1, `comments`.`body` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id WHERE (comments.created_at > '2008-05-18 18:06:34') 

因此,连接表上的嵌套连接、条件等应该仍然可以正常工作。总的来说,它应该会加快您的查询速度。有人报告说,由于更多的个人查询,MySQL 似乎在 CPU 方面受到了更强的冲击。您是否在家工作并进行压力测试和基准测试,看看会发生什么。

关于ruby-on-rails - activerecord has_many :through find with one sql call,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1701961/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com