如果凌乱的控制器更快,为什么Rails范围更可取?

时间:2010-11-30 13:30:03

标签: ruby-on-rails activerecord arel named-scopes

我一直在尝试使用范围链接Arel查询,而不是仅使用我在控制器中编写的一些冗长的逻辑。但是范围比获取所有记录慢,然后用一些逻辑筛选它们。我想知道范围更好的原因。

这就是我正在做的事情:

  • 一个问题有很多答案
  • 答案属于一个问题
  • 一个问题有一个“question_type”列,我用它来排序

首先,范围方式......

质疑.rb:

scope :answered, joins(:answers).order('answers.created_at desc')
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")

在questions_controller.rb中:

@dogs_recently_answered = Question.answered.dogs.uniq[0..9]
@cats_recently_answered = Question.answered.cats.uniq[0..9]
@mermaids_recently_answered = Question.answered.mermaids.uniq[0..9]

然后在视图中,我循环遍历那些实例变量(现在是包含最多10个元素的数组)并显示结果。

以下是加载页面所需的时间(五次不同):

在535ms完成200 OK(浏览次数:189.6ms | ActiveRecord:46.2ms)

在573ms完成200 OK(浏览次数:186.0ms | ActiveRecord:46.3ms)

在577ms完成200 OK(浏览次数:189.0ms | ActiveRecord:45.6ms)

在532ms完成200 OK(浏览次数:182.9ms | ActiveRecord:46.1ms)

在577ms完成200 OK(浏览次数:186.7ms | ActiveRecord:46.9ms)

现在,凌乱的控制器方式......

@answers = Answer.order("created_at desc")
@all_answered = []
@answers.each {|answer| @all_answered << answer.question}
@recently_answered = @all_answered.uniq
@dogs_all_answered = []
@cats_all_answered = []
@mermaids_all_answered = []
@recently_answered.each do |q|
  if q.question_type == "dogs"
    @dogs_all_answered << q
    @dogs_recently_answered = @dogs_all_answered[0..9]
  elsif q.question_type == "cats"
    @cats_all_answered << q
    @cats_recently_answered = @cats_all_answered[0..9]
  elsif q.question_type == "mermaids"
    @mermaids_all_answered << q
    @mermaids_recently_answered = @mermaids_all_answered[0..9]
  end
end

以下是现在加载页面所需的时间(五次不同):

在475ms完成200 OK(浏览次数:196.5ms | ActiveRecord:34.5ms)

在480毫秒内完成200 OK(浏览次数:200.4ms | ActiveRecord:36.4ms)

在434ms完成200 OK(浏览次数:198.2ms | ActiveRecord:35.8ms)

在475ms完成200 OK(浏览次数:194.2ms | ActiveRecord:36.4ms)

在475ms完成200 OK(浏览次数:195.0ms | ActiveRecord:35.4ms)

因此...

除了可读性之外,通过使用范围修改查询可以获得什么?当有更多记录时,它最终会变得更快吗?

2 个答案:

答案 0 :(得分:4)

首先,我不确定我是否理解除了唯一之外的问题是什么,所以我试着去除它。我不知道您的数据的逻辑,因此可能不适用,但这是您可以避免的额外步骤。

以下是我如何处理它:

scope :answered, joins(:answers).order('answers.created_at desc')
scope :recent, take(10)
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")

@dogs_recently_answered = Question.answered.dogs.recent
@cats_recently_answered = Question.answered.dogs.recent
@mermaids_recently_answered = Question.answered.dogs.recent

这会将查询的TOP部分转移到它所属的数据库,而不是取出所有行,然后丢弃除10之外的所有内容。根据您的统一标准,您可能会也使用像

这样的范围
scope :unique, select('DISTINCT column_name')

然后你可以使用Question.cats.unique.recent并在一个利用数据库系统设计的关系代数的快速查询中得到它。

答案 1 :(得分:1)

我认为在这种情况下范围较慢的原因是因为它们导致3个单独的数据库查询,而另一种方法使用的知识是,您使用的单个查询可以满足所有三个结果。

假设情况确实如此,范围正在进行3次单独的查询就不足为奇了,因为系统不知道你何时调用第一个你之后要调用其他查询的查询。也许有一个对这种情况有意义的优化策略,但我不知道ActiveRecord是否实现了它。

无论如何,这是该特定情况下范围的一个缺点。我喜欢范围,因为它们干净/清晰,灵活并且封装了查询的命名抽象。 AFAICT,在许多情况下,它们并不比同等的直接查询慢得多。