简体   繁体   English

为什么Rails ActiveRecord会两次访问数据库?

[英]Why is Rails ActiveRecord hitting the database twice?

@integration = Integration.first(:conditions=> {:integration_name => params[:integration_name]}, :joins => :broker, :select => ['`integrations`.*, `brokers`.*'])
$stderr.puts @integration.broker.id # This line causes Brokers to be queried again

Results in: 结果是:

Integration Load (0.4ms)   SELECT `integrations`.*, `brokers`.* FROM `integrations` INNER JOIN `brokers` ON `brokers`.id = `integrations`.broker_id WHERE (`integrations`.`integration_name` = 'chicke') LIMIT 1
Integration Columns (1.5ms)   SHOW FIELDS FROM `integrations`
Broker Columns (1.6ms)   SHOW FIELDS FROM `brokers`
Broker Load (0.3ms)   SELECT * FROM `brokers` WHERE (`brokers`.`id` = 1) 

Any ideas why Rails would hit the databse again for brokers even though I already joined/selected them? 任何想法为什么Rails会再次为brokers打到数据库,即使我已经加入/选择了它们?

Here are the models (Broker -> Integration is a 1-to-many relationship). 以下是模型(Broker - > Integration是1对多关系)。 Note that this is incomplete, and I have only included the lines that establish their relationship 请注意,这是不完整的,我只包括建立关系的行

class Broker < ActiveRecord::Base

  # ActiveRecord Associations
  has_many :integrations

class Integration < ActiveRecord::Base

  belongs_to :broker

I'm using Rails/ActiveRecord 2.3.14, so keep that in mind. 我正在使用Rails / ActiveRecord 2.3.14,所以请记住这一点。

When I do Integration.first(:conditions=> {:integration_name => params[:integration_name]}, :include => :broker) that line causes two SELECT s 当我执行Integration.first(:conditions=> {:integration_name => params[:integration_name]}, :include => :broker) ,该行导致两个SELECT

Integration Load (0.6ms)   SELECT * FROM `integrations` WHERE (`integrations`.`integration_name` = 'chicke') LIMIT 1
  Integration Columns (2.4ms)   SHOW FIELDS FROM `integrations`
  Broker Columns (1.9ms)   SHOW FIELDS FROM `brokers`
  Broker Load (0.3ms)   SELECT * FROM `brokers` WHERE (`brokers`.`id` = 1) 

Use include instead of joins to avoid reloading Broker object. 使用include而不是joins来避免重新加载Broker对象。

Integration.first(:conditions=>{:integration_name => params[:integration_name]}, 
  :include => :broker)

There is no need to give the select clause as you are not trying to normalize the brokers table columns. 没有必要给出select子句,因为您没有尝试规范化brokers表列。

Note 1: 注1:

While eager loading dependencies, AR executes one SQL per dependency. 在急切加载依赖项时,AR每个依赖项执行一个SQL。 In your case AR will execute main sql + broker sql. 在你的情况下,AR将执行主sql + broker sql。 Since you are trying to get one row there isn't much gain. 既然你试图获得一排就没有多少收益。 When you are trying to access N rows you will avoid the N+1 problem if you eager-load the dependencies. 当您尝试访问N行时,如果您急切加载依赖项,则将避免N + 1问题。

Note 2: 笔记2:

In some cases it might be beneficial to use custom eager loading strategies. 在某些情况下,使用自定义预先加载策略可能是有益的。 Let us assume that you just want to get the associated broker name for the integration. 我们假设您只想获取集成的关联代理名称。 You can optimize your sql as follows: 您可以按如下方式优化您的sql:

integration = Integration.first(
  :select => "integrations.*, brokers.name broker_name",
  :conditions=>{:integration_name => params[:integration_name]}, 
  :joins => :broker)

integration.broker_name # prints the broker name

The object returned by the query will have all the aliased columns in the select clause. 查询返回的对象将包含select子句中的所有别名列。

Above solution will not work when you want to return the Integration object even when there is no corresponding Broker object. 当您想要返回Integration对象时,即使没有相应的Broker对象,上述解决方案也不起作用。 You have to use OUTER JOIN . 你必须使用OUTER JOIN

integration = Integration.first(
  :select => "integrations.*, brokers.name broker_name",
  :conditions=>{:integration_name => params[:integration_name]}, 
  :joins => "LEFT OUTER JOIN brokers ON brokers.integration_id = integrations.id")

The :joins options just makes active record add a join clause to the query. :joins选项只是使活动记录为查询添加一个join子句。 It doesn't actually make active record do anything with the rows that have been returned. 它实际上并不使活动记录对已返回的行执行任何操作。 The association isn't loaded and so accessing it triggers a query 未加载关联,因此访问它会触发查询

The :include option is all about loading associations ahead of time. :include选项是关于提前加载关联的全部内容。 Active record has two strategies for doing this. 活动记录有两种策略可以做到这一点。 One is via a big join query and one is by firing one query per association. 一个是通过一个大的连接查询,一个是通过每个关联触发一个查询。 The default is the latter, which is why you see two queries. 默认值是后者,这就是您看到两个查询的原因。

On rails 3.x you can decide which of those strategies you want by doing Integration.preload(:broker) or Integration.eager_graph(:broker) . 在rails 3.x上,您可以通过执行Integration.preload(:broker)Integration.eager_graph(:broker)来决定您需要哪些策略。

There is no such facility in rails 2.x, so the only thing you can do is trick the heuristics used to determine the strategy. rails 2.x中没有这样的工具,所以你唯一能做的就是欺骗用于确定策略的启发式方法。 Whenever rails thinks that the order clause, select clause or conditions refer to columns on the included association it switches to the joins strategy (because it is the only one that works in that case). 每当rails认为order子句,select子句或条件引用包含的关联上的列时,它就会切换到连接策略(因为它是唯一一个在这种情况下工作的策略)。

For example doing something like 例如做类似的事情

Integration.first(:conditions => {...}, :include => :broker, :select => 'brokers.id as ignored')

should force the alternate strategy (and active record actually ignores the select option in this case). 应强制备用策略(在这种情况下,活动记录实际上忽略了选择选项)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM