【问题标题】:Why are Rails scopes preferable, if messy controllers are faster?如果凌乱的控制器更快,为什么 Rails 范围更受欢迎?
【发布时间】:2011-05-17 21:20:00
【问题描述】:

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

这就是我正在做的事情:

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

首先,作用域方式...

in question.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 个元素的数组)并显示结果。

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

在 535 毫秒内完成 200 次 OK(查看次数:189.6 毫秒 | ActiveRecord:46.2 毫秒)

在 573 毫秒内完成 200 次 OK(查看次数:186.0 毫秒 | ActiveRecord:46.3 毫秒)

在 577 毫秒内完成 200 次 OK(查看次数:189.0 毫秒 | ActiveRecord:45.6 毫秒)

在 532 毫秒内完成 200 次 OK(查看次数:182.9 毫秒 | ActiveRecord:46.1 毫秒)

在 577 毫秒内完成 200 次 OK(查看次数:186.7 毫秒 | ActiveRecord:46.9 毫秒)

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

@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

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

在 475 毫秒内完成 200 次 OK(查看次数:196.5 毫秒 | ActiveRecord:34.5 毫秒)

在 480 毫秒内完成 200 次 OK(查看次数:200.4 毫秒 | ActiveRecord:36.4 毫秒)

在 434 毫秒内完成 200 次 OK(查看次数:198.2 毫秒 | ActiveRecord:35.8 毫秒)

在 475 毫秒内完成 200 次 OK(查看次数:194.2 毫秒 | ActiveRecord:36.4 毫秒)

在 475 毫秒内完成 200 次 OK(查看次数:195.0 毫秒 | ActiveRecord:35.4 毫秒)

那么...

除了可读性之外,使用范围磨练查询还有什么好处?当记录更多时,它最终会变得更快吗?

【问题讨论】:

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


    【解决方案1】:

    首先,我不确定我是否理解一个问题除了独特性之外的其他原因,因此我会考虑尝试将其删除。我不知道您的数据的逻辑,因此这可能不适用,但这是您可以避免的额外步骤。

    以下是我的处理方式:

    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 并在一个快速查询中获取所有信息,该查询利用数据库系统设计的关系代数。

    【讨论】:

    • 如果一个问题有 3 个答案,它会列出该问题 3 次。这就是为什么它需要 uniq 。我已经避免了 select('DISTINCT ...') 因为选择不同的问题太棘手了,然后按每个问题的最新 answer 排序问题。只需调用“uniq”并将其转换为一系列独特的问题,就证明了这一点要容易得多。
    • 在这种情况下,我会说你应该加入问题的答案,按答案降序排序,并按问题分组以获得问题的最新答案。我的一般规则是将这类事情的大部分负担转移到数据库,即使这意味着重构我自己对问题的看法,因为数据库比我更聪明,而且我可以免费获得未来的优化。
    • 再看一遍,也许现在我在第一个范围内进行排序并不重要。但是我不熟悉 take(...),Rails: undefined method `take' for #<0x0000010386fca0>
    【解决方案2】:

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

    假设是这种情况,范围执行 3 个单独的查询也就不足为奇了,因为系统不知道您何时调用第一个查询,之后您将立即调用其他查询。也许有一种优化策略对这种情况是明智的,但我不知道 ActiveRecord 实现了它。

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

    【讨论】:

    • 你确定有 3 个分贝命中吗?通常在控制台中查看 Question.answered.dogs.to_sql 时,您会发现 ActiveRecord 在触发之前创建了一个 SQL 查询。
    • 正确,但是,问题中的场景调用了 3 个单独的范围链(Question.answered.dogs.uniq[0..9]、Question.answered.cats.uniq[0..9]和 Question.answered.mermaids.uniq[0..9]),每个都是一个 SQL 查询。
    • 他的意思是三个数据库命中实例化三个ivars。
    • 是的,事实上,当考虑一个作用域链时,“系统”确实知道所有调用的作用域并使用这些信息来形成 SQL 查询。我在回答中指的是系统不知道您何时调用 Question.answered.dogs.uniq[0..9],您将调用 Question.answered.cats.uniq[0..9]下一行,因此不会将两者捆绑在一起成为更有效的查询。然而,“凌乱”的解决方案是在编写时了解如何一次性满足所有内容的。
    • 哈,我没有解析出你也丢弃了数组切片的一大堆结果,正如 jxpx777 正确识别的那样,这也使得每个查询返回的数据都比需要的多(数据很容易在 SQL 级别排除,从而加快每个查询)。
    猜你喜欢
    • 2019-06-11
    • 2020-10-25
    • 2012-11-25
    • 2011-04-02
    • 2018-08-31
    • 2011-07-10
    • 2012-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多