我开发了一个应用程序,为登录用户提供每月统计信息的概述。
这是我目前的做法:
Statistics.html.haml:
#(@parsed months is an array of monthnames.)
- @parsed_months.each do |month|
= render :partial => "statistic", :locals => {:month => month}
_statistic.html.haml:
%tr{:class => cycle("odd", "even")}
%td= l(month, :format => "%B").capitalize
%td= current_user.total_views_count(month)
%td= current_user.total_leads_count(month)
%td= current_user.total_clicks_count(month)
返回总视图的方法(在User.rb中):
def total_views_count(month = nil)
if month == nil
v = 0
self.companies.each {|c| v += c.counts.size}
return v
else
v = 0
self.companies.each {|c| v += c.counts.where(:created_at => Date.today.beginning_of_year..Date.today.end_of_year).where(:created_at => month.beginning_of_month..month.end_of_month).size}
return v
end
end
Company.rb:
belongs_to :user
has_many :counts, :as => :countable, :dependent => :destroy
Count.rb:
belongs_to :countable, :polymorphic => true
User.rb:
has_many :companies
这表现不错,但几个月后,Count模型已经增长到一百万条+记录,导致heroku请求超时。
我可以做些什么来优化此查询,或者有更好的方法来执行此操作?
提前致谢!
答案 0 :(得分:1)
您应该注意以下优化查询:
第2点和第3点与@opensourcechris提到的相同。
我有一段时间没有使用活动记录,所以我不能给你查询的arel语法,但主要问题是因为有很多数据,你在一个请求中做了很多繁重的查询。您应该使用连接来减少查询并仔细使用索引来使连接和查询最佳。使用连接查询将如下所示:
SELECT count(c.id) FROM users u
JOIN companies comp ON comp.user_id = u.id
JOIN counts c ON c.company_id = comp.id
AND c.countable_type = 'Company'
AND c.created_at BETWEEN date_range
WHERE u.id = currrent_user_id
您也可以在此处使用GROUP BY
在一个查询中检索所有月份的数据,并按月保留计数。
要使联接有效工作,您应该在companies.user_id
上建立索引,在counts.countable_id, counts.countable_type, counts.created_at
上设置复合索引。
现在应该这样做,但是由于数量已经增加到数百万加上数字,这在短期内无法解决问题。随着计数表的增长,即使这个查询也会开始变慢。在关系数据库中,查询时间随着行数的增加几乎呈线性增加,但在某个阈值之后,它开始以更快的速度增长。因此,通常需要包含您需要使用的表的大小。那是滚动表进入画面的时候。
随着这一数量的数据进入插入速度也是一个问题。因此,您应该创建一个没有任何索引的表,并记录该表中的所有计数数据。数据可以定期汇总到其他表中。可以根据报告的粒度创建汇总表。常见选项是每小时,每日,每周,每月和每年汇总表。
还可以将数据转储到存档表中以保留历史记录,以便可以使用不同的粒度或其他要求随时重新创建汇总表。将数据转储到存档表中后,可以从主表中清除它,以便插入速度不会随着时间的推移而受到影响。它还允许记录任何视图,而不必担心像10分钟规则这样的约束,因为数据可以在滚动之前清理。
PS:我猜你应该使用session_id(uuid)和ip地址来正确计算视图。通常,许多互联网用户共享公共IP地址。
答案 1 :(得分:0)
要优化查询,您应该首先审核每个表上的索引。由于您的WHERE位于日期字段上,我认为索引可以很好地使用它来查看索引:
USE *database*;
SHOW INDEX FROM *tablename*;
然后确保您索引列的位置。
另一种选择是忘记计算历史月份的总数,只需在月末计算它们并将它们存储在新的汇总表中。因此,您将动态计算当前月份的数据,可以从新的汇总表中返回前几个月。