1

我开发了一个应用程序,为登录的用户提供每月统计数据的概览。

这是我目前的做法:

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

公司.rb:

belongs_to :user
has_many :counts, :as => :countable, :dependent => :destroy

计数.rb:

belongs_to :countable, :polymorphic => true

用户.rb:

has_many :companies

这表现不错,但几个月后 Count 模型已经增长到一百万多条记录,导致 heroku 上的请求超时。

我可以做些什么来优化这个查询或者有更好的方法来做到这一点?

提前致谢!

4

2 回答 2

1

您应该注意以下几点来优化查询:

  1. 尝试减少单个请求中的查询
  2. 优化索引
  3. 创建汇总表

第 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此处在单个查询中检索所有月份的数据,并且每月保留计数。

为了使连接有效地工作,您应该有一个索引 oncompanies.user_id和一个复合索引 on counts.countable_id, counts.countable_type, counts.created_at

现在应该这样做,但是随着计数在短短几个月内增长到数百万以上,从长远来看,这并不能解决问题。随着 counts 表的增长,即使这个查询也会开始变慢。在关系数据库中,查询时间几乎随着行数的增加而线性增加,但在某个阈值之后,它开始以更快的速度增长。因此,包含您需要经常使用的表的大小总是明智的。这就是滚动表出现的时候。

随着数据量的增加,插入速度也是一个问题。因此,您可能应该创建一个没有任何索引的表并将所有计数数据记录在该表中。可以定期将数据汇总到其他表中。可以根据需要在粒度上创建汇总表以进行报告。常见的选项是每小时、每天、每周、每月和每年的汇总表。

数据也可以转储到存档表中以保留历史记录,以便可以随时以不同的粒度或其他要求重新创建汇总表。将数据转储到存档表后,可以将其从主表中清除,这样插入速度就不会随着时间的推移而受到影响。它还允许记录任何视图,而不必担心像 10 分钟规则这样的限制,因为可以在滚动之前清理数据。

PS:我想你应该使用 session_id(uuid) 和 ip 地址来正确计算视图。通常,许多互联网用户共享一个公共 IP 地址。

于 2012-06-25T22:48:11.263 回答
0

要优化查询,您应该首先审核每个表上的索引。由于您的 WHERE 在日期字段上,我认为索引可以很好地使用它来查看您的索引:

USE *database*;
SHOW INDEX FROM *tablename*;

然后确保您正在索引您的 where 列。

另一种选择是忘记计算历史月份的总计,只需在月底计算它们并将它们存储在新的汇总表中。因此,您将即时计算的是当月的数据,可以从新的汇总表中返回前几个月。

于 2012-06-25T18:35:14.897 回答