欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ActiveRecord 查询优化 博客分类: Ruby ActiveRecordRubyOracle应用服务器Rails 

程序员文章站 2024-03-19 20:35:28
...
ActiveRecord使用很方便,只需要声明映射关系,就可以方便地获取各个关联对象,而且是延迟加载。有时候这种关系延迟加载可能会严重影响性能,比如下面这个例子:
class User < ActiveRecord::Base
end

class Post < ActiveRecord::Base
  has_many :replies
end

class Reply < ActiveRecord::Base
  belongs_to :user
  belongs_to :post
end

我们在查询某一个帖子的同时,希望取到所有回复以及回复者。
@post = Post.find(params[:id])

<%= @post.body %>
<hr />
<% @post.replies.each do |reply| -%>
  <%= reply.body %><br />Post by: <%= reply.user.name %><hr />
<% end -%>

如果你查看日志,就会发现一大堆的查询,类似于:
SELECT * FROM users WHERE id = xx limit 1

这个对性能的影响还是很严重的,所以需要改进查询,在查询出reply的同时把user也查询出来。把它修改为:
@post = Post.find(params[:id], :include => {:replies => :user})

这次所有查询都在一条语句中进行了,不过某些情况下你可以会发现它反而会降低查询效率,原因在于本来只有1条的post记录也被扩展到多条了,这时候还是分开查询为好:
@post = Post.find(params[:id])
@replies = @post.replies.find(:all, :include => :user)

是的,这次总共只有2条语句,一般情况下效率都会提升许多。


使用find with :include可以提高效率,但尽量不要对一对多关系使用。如果只是一对一关系,那么即便是很多层的关联,效率也还不错。比如这样:
Street.find(1, :include => {:city => {:province => {:country => :continent}}})


有时我们还是要查询一些一对多关系,这时候也可以采用其它一些技巧,例如下面这个查询:
Comment.find(1, :include => {:user => {:replies => :post}})

我把它理解为:查询某一评论(comment),同时查出发表这一评论的用户发表过的回复帖子,以及这些帖子所回复的主题。当然这种查询可能是不合理的,只是想探讨一下如何优化这种查询。

由于数据库查询的效率往往比服务器处理要低,而且服务器很容易扩展而数据库集群则难以部署,所以我们还是要尽量减少查询次数,同时不增加查询数据量。

这里我的想法是把它分成2次查询:
@comments = Comment.find(:all, :include => :user)
user_ids = [0].concat(@comments.map(&:user).map(&:id))
replies = Replies.find(:all, :conditions => ["user_id in (?)", user_ids], :include => :post)
@user_replies = {}
replies.each {|r| @user_replies[r.user_id] ||= []; @user_replies[r.user_id] << r}

然后直接使用就可以了,在要显示某用户的回复及这些回复的主题时,只要把user.id作为key从@user_replies里提取。使用[0].concat是防止可能构造出一个空数组,查询时会生成一个非法SQL语句,这里也假设0是一个无效的ID值。

我曾经在一些查询中使用这个做法,结果数据库查询时间缩短了近10倍,整个action的处理效率也因此提高了4倍。