我有以下模型,允许用户对照片进行投票。
class Vote < ActiveRecord::Base
attr_accessible :value
belongs_to :photo
belongs_to :user
validates_associated :photo, :user
validates_uniqueness_of :user_id, :scope => :photo_id
validates_uniqueness_of :photo_id, :scope => :user_id
validates_inclusion_of :value, :in => [-2,-1,1,2], :allow_nil => true
after_save :write_photo_data
def self.score
dd = where( :value => -2 ).count
d = where( :value => -1 ).count
u = where( :value => 1 ).count
uu = where( :value => 2 ).count
self.compute_score(dd,d,u,uu)
end
def self.compute_score(dd, d, u, uu)
tot = [dd,d,u,uu].sum.to_f
score = [-5*dd, -2*d, 2*u, 5*uu].sum / [tot,4].sum*20.0
score.round(2)
end
private
def write_photo_data
self.photo.score = self.photo.votes.score
self.photo.save!
end
end
这功能很好,但计算一张照片的分数相当慢——似乎平均需要7-12秒。我尝试为photo_id
、user_id
添加索引,并为photo_id
和value
添加一个组合索引,但据我所知,这并没有真正提高性能。
我很想听听任何一位认真的铁路专家(我完全是个业余爱好者)对如何优化/改进这一点的反馈。你会如何统计一张特定照片的选票和价值?
谢谢!
--编辑--
注意,得分:-2,-1,1,2
表示"两个大拇指向下,一个大拇指向下、大拇指向上、两个大拇指向上",而不是特定值。我可以将这些值与我在计算分数方法中分配给它们的值相匹配,但到目前为止我还没有这样做,因为我可能想在看到更多的数据积累后,随着时间的推移调整权重。
此外,无论我如何在DB中代表这四张可能的选票,我仍然需要每种选票的计数以及每张照片的选票加权值来计算分数。谢谢
您需要一个值索引。只有当查询同时包含两个组件(从左边开始)时,组合索引才起作用。由于where子句没有指定照片id,因此它没有使用组合索引。
更新请参阅http://dev.mysql.com/doc/refman/5.0/en/multiple-column-indexes.html
您可以做的一件事是向数据库询问一次而不是四次分数计数:
Vote.where(photo_id: photo.id).group(:value).count
会导致一个数据库查询,并给你一个类似的散列
{-2 => 21, -1 => 48, 1 => 103, 2 => 84}
除此之外,如果在数据库中存储[-5, -2, 2, 5]
而不是[-2, -1, 1, 2]
的实际值,则可以只执行
Vote.where(photo_id: photo.id).sum
并直接从数据库中获得总和(甚至使用avg
来获得平均值)
为什么存储-2,2,1,2而不是实际的等级?如果您存储了分数(例如-5),您将能够直接在DB中计算分数,而不必运行4个计数查询。这肯定是一个进步。
如果数据库中有很多记录,那么在值列上设置索引将加快SELECTs的速度。
上面的帖子也提出了一些关于直接优化的好观点。然而,随着数据库的扩展,所有这些方法最终都会失败。由于分数是一个派生值,您可以将其缓存在Memcached、Redis甚至SQL中,这将确保获取分数随着应用程序的增长而在恒定的时间内扩展。您可以允许缓存过期,并使用后台进程保持更新。通过这样做,您的计算功能可以花费任意长的时间,而不会影响用户体验。