1

我第一次尝试优化活动记录中的一些 N+1 查询。有 3 个要杀死 - 2 个很容易通过 .includes 调用,但我终其一生都无法弄清楚为什么第三个仍在调用一堆查询。下面的相关代码 - 如果有人有任何建议,我会非常感激。

控制器:

@enquiries = Comment.includes(:children).faqs_for_project(@project)

模型;

def self.faqs_for_project(project)
  Comment.for_project_and_enquiries(project, project.enquiries).where(:published => true).order("created_at DESC")
end

(及相关范围)

scope :for_project_and_enquiries, lambda{|p, qs| where('(commentable_type = ? and commentable_id = ?) or (commentable_type = ? and commentable_id IN (?))', "Project", p.id, "Enquiry", qs.collect{|q| q.id})}

看法:

...
= render :partial => 'comments/comment', :collection => @enquries
...

(以及部分中的违规行)

...
= 'Read by ' + pluralize(comment.acknowledgers.count, 'lead')
...

为每个评论调用两个 SQL 查询。2个查询是:

SQL (2.8ms)  SELECT COUNT(*) FROM "users" INNER JOIN "acknowledgements" ON "users".id = "acknowledgements".user_id WHERE (("acknowledgements".feedback_type = 'Comment') AND ("acknowledgements".feedback_id = 177621))
CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1295 LIMIT 1

我原以为将 (:user, :acknowledgements) 附加到控制器的 .includes 中可以解决问题,但它似乎没有任何效果。如果有人对我所缺少的有任何建议,我将非常感激

4

1 回答 1

3

我相信在您的Comment表中您想添加一:acknowledgers_count列作为计数器缓存

has_many :acknowledgers, ....., counter_cache: true

您将需要创建迁移以将:acknowledgers_count列添加到comments表中。Rails 应该负责其余的工作。

您可以在此处了解有关ActiveRecord::CounterCacheapi的更多信息。

count方法 in在 ActiveRecord 中重载,comment.acknowledgers.count首先检查计数器缓存列是否存在,如果存在,则直接从模型(在本例中为Comment模型)返回,而无需再次访问数据库。

最后,最近有一个很棒的Railscast关于 gem 调用Bullet可以帮助您识别这些查询问题并指导您找到解决方案。它涵盖了计数器缓存和 N+1 查询。

正如@ismaelga 在对此答案的评论中指出的那样,调用.size而不是调用.count关系通常是一种更好的做法。查看代码size

def size
  loaded? ? @records.length : count
end

如果关系已经加载,它只会调用length它,否则它会调用count. 这是一个额外的检查,试图防止不必要地查询数据库。

于 2012-08-29T23:45:23.253 回答