ActiveRecord 查询优化

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])

[code]
<%= @post.body %>
<hr />
<% @post.replies.each do |reply| -%>
<%= reply.body %><br />Post by: <%= reply.user.name %><hr />
<% end -%>
[/code]
如果你查看日志,就会发现一大堆的查询,类似于:

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倍。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值