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

bgc*_*ode 2 sql activerecord ruby-on-rails

@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
Run Code Online (Sandbox Code Playgroud)

结果是:

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) 
Run Code Online (Sandbox Code Playgroud)

任何想法为什么Rails会再次击中数据库,brokers即使我已经加入/选择它们?

以下是模型(Broker - > Integration是1对多关系).请注意,这是不完整的,我只包括建立关系的行

class Broker < ActiveRecord::Base

  # ActiveRecord Associations
  has_many :integrations

class Integration < ActiveRecord::Base

  belongs_to :broker
Run Code Online (Sandbox Code Playgroud)

我正在使用Rails/ActiveRecord 2.3.14,所以请记住这一点.

当我做Integration.first(:conditions=> {:integration_name => params[:integration_name]}, :include => :broker)那条线导致两个SELECTs

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) 
Run Code Online (Sandbox Code Playgroud)

Har*_*tty 8

使用include而不是joins避免重新加载Broker对象.

Integration.first(:conditions=>{:integration_name => params[:integration_name]}, 
  :include => :broker)
Run Code Online (Sandbox Code Playgroud)

select由于您没有尝试规范化brokers表列,因此无需提供该子句.

注1:

在急切加载依赖项时,AR每个依赖项执行一个SQL.在你的情况下,AR将执行主sql + brokersql.既然你试图获得一排就没有多少收益.当您尝试访问N行时,如果您急切加载依赖项,则将避免N + 1问题.

笔记2:

在某些情况下,使用自定义预先加载策略可能是有益的.我们假设您只想获取集成的关联代理名称.您可以按如下方式优化您的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
Run Code Online (Sandbox Code Playgroud)

查询返回的对象将包含select子句中的所有别名列.

当您想要返回Integration对象时,即使没有相应的Broker对象,上述解决方案也不起作用.你必须使用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")
Run Code Online (Sandbox Code Playgroud)