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


@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

结果:

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) 

知道为什么 Rails 会再次进入数据库brokers即使我已经加入/选择了它们?

以下是模型(代理 -> 集成是一对多关系)。 请注意,这是不完整的,我只包括了建立他们关系的行

class Broker < ActiveRecord::Base
  # ActiveRecord Associations
  has_many :integrations
class Integration < ActiveRecord::Base
  belongs_to :broker

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

当我这样做时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) 

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

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

无需给出 select 子句,因为您没有尝试规范化brokers表列。

注1:

在预先加载依赖项时,AR 会为每个依赖项执行一个 SQL。在您的情况下,AR 将执行主 sql + broker sql。由于您试图获得一行,因此没有太多收益。当您尝试访问 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

查询返回的对象将具有 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")

:joins选项只是使活动记录向查询添加联接子句。它实际上并没有使活动记录对已返回的行执行任何操作。关联未加载,因此访问它会触发查询

:include选项完全是关于提前加载关联。活动记录有两种策略来执行此操作。一种是通过大型联接查询,另一种是通过为每个关联触发一个查询。默认值为后者,这就是您看到两个查询的原因。

在rails 3.x上,您可以通过执行Integration.preload(:broker)Integration.eager_graph(:broker)来决定所需的策略中的哪一种。

rails 2.x 中没有这样的工具,所以你唯一能做的就是欺骗用于确定策略的启发式方法。每当 rails 认为顺序子句、select 子句或条件引用所包含关联的列时,它就会切换到连接策略(因为它是唯一在这种情况下有效的策略)。

例如做类似的事情

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

应强制使用备用策略(在这种情况下,活动记录实际上忽略了选择选项)。

最新更新