【问题标题】:Best practice: Which information should I store in my database?最佳实践:我应该将哪些信息存储在我的数据库中?
【发布时间】:2016-02-05 09:13:20
【问题描述】:

目前我正在开发一个小型图书评分应用程序,用户可以在其中对图书进行评分和评论。

我当然有书本模型:

class Book < ActiveRecord::Base
  has_many :ratings
end

和评级模型:

class Rating < ActiveRecord::Base
  belongs_to :book
end

评分对象的“整体评分值”是由不同的评分类别(例如可读性、...)计算得出的。此外,一本书的总体评分应该由所有给定的评分来计算。

现在我问自己的问题是:我应该在每次有人访问我的页面时计算/查询每本书的总体评分,还是应该在我的图书模型中添加一个字段来(定期)计算和保存总体评分?

编辑:在这种情况下我将使用的“计算”是一个简单的平均确定。

示例:一本书有大约 200 个评分。每个评级都是 10 个类别评级的组合。所以我想确定一个评分的平均值,以及所有 200 个评分的平均值。

【问题讨论】:

  • 你能告诉我们更多关于这个计算的性质吗?如果它像数值平均值这样简单,我会选择在请求时在查询中动态计算它。
  • @TimBiegeleisen 是的,这将是一个简单的平均确定。更新了我的问题

标签: mysql ruby-on-rails


【解决方案1】:

如果这些评级的平均计算成本不高(即不需要很长时间),那么只需即时计算即可。这符合不过早优化的想法(请参阅http://c2.com/cgi/wiki?PrematureOptimization)。

但是,如果您确实想优化此计算,则可以将其存储在图书模型中并更新评级写入的计算。这被称为“缓存”结果。这是一些将在数据库中缓存平均评分的代码。 (还有其他缓存方式)。

class Book < ActiveRecord::Base
  has_many :ratings, after_add :update_average_rating

  def update_average_rating
    update_attribute(:average_rating, average_rating)
  end

  def average_rating
    rating_sum / ratings.count
  end

  def rating_sum
    ratings.reduce(0) {|sum, rating|
      sum + rating.value # assuming rating model has a value attribute
    }
  end
end

class Rating < ActiveRecord::Base
  belongs_to :book
end

注意:以上代码假设在您的数据库中您的 book 表上存在 average_rating 列。请记住使用迁移添加此列。

【讨论】:

  • 很好的答案,但是让数据库使用ActiveRecord::Calculations 来完成这项工作真的很容易,并且您可以避免将大量记录拉入服务器内存的潜在内存问题。大多数 RDBMS:es 也非常擅长非常有效地进行计算。
  • 您可能还想对 Book 中的 rating 关联使用回调,而不是让 Rating 负责维护 Book 的状态(因为 SRP)。 has_many :ratings, after_add: :update_average_rating
  • @max.关于 SRP 的要点。我已经编辑了我的答案。关于数据库级别的计算:是的,你是对的。我看到你自己添加了这个答案。
【解决方案2】:

数据库

最有效(虽然不是常规)的方法是使用 db 级别的 ALIAS 列,允许您计算每个 book 调用的评级的 AVGSUM

#app/models/book.rb
class Book < ActiveRecord::Base
   def reviews_avg category
      cat = category ? "AND `category` = \"#{category}\"" : ""
      sql = "SELECT AVG(`rating`) FROM `reviews` WHERE `book_id` = #{self.id} #{cat})
      results = ActiveRecord::Base.connection.execute(sql)
      results.first.first.to_f
   end
end

这将允许:

@book = Book.find x
@book.reviews_avg               # -> 3.5
@book.reviews_avg "readability" # -> 5

这是最有效的,因为它完全由 DB 处理:


导轨

应该使用 Rails 的 average 功能:

#app/models/book.rb
class Book < ActiveRecord::Base
   has_many :ratings do
      def average category
        if category
          where(category: category).average(:rating)
        else
          average(:rating)
        end
      end
   end
end

以上内容将使您能够调用@book实例,并评估averagetotal 的评级:

@book = Book.find x
@book.reviews.average               #-> 3.5
@book.reviews.average "readability" #-> 5

--

您也可以在Review 上使用class method / scope

#app/models.review.rb
class Review < ActiveRecord::Base
   scope :avg, (category) -> { where(category: category).average(:rating) }
end

这将允许您调用:

@book = Book.find x
@book.reviews.avg               #-> 3.5
@book.reviews.avg "readability" #-> 5

关联扩展

另一种方法(未测试)是在ActiveRecord Association Extension 中使用proxy_association.target 对象。

虽然不如数据库级查询高效,但它可以让您在内存中执行活动:

#app/models/book.rb
class Book < ActiveRecord::Base
   has_many :reviews do
     def avg category
       associative_array = proxy_association.target
       associative_array = associative_array.select{|key, hash| hash["category"] == category } if category
       ratings = associative_array.map { |a| a["rating"] }
       ratings.inject(:+) / associative_array.size #-> 35/5 = 7
     end
   end
end

这将允许您调用:

@book = Book.find x
@book.reviews.avg                # -> 3.5
@book.reviews.avg "readability"  # -> 5

【讨论】:

    【解决方案3】:

    根本没有必要重新计算每次页面访问的平均总体评分,因为只有当有人实际对图书进行评分时,它才会改变。因此,只需使用字段 AVG_RATING 或类似的字段并更新每个给定评分的值。

    【讨论】:

      【解决方案4】:

      您是否考虑使用评级的缓存版本。

      rating = Rails.cache.fetch("book_#{id}_rating", expires_in: 5.minutes) do
        do the actual rating calculation here
      end
      

      【讨论】:

        【解决方案5】:

        在大多数情况下,您只需查询数据库即可获得平均值:

        average = book.reviews.average(:rating)
        

        而且在大多数情况下,它不会太昂贵以至于查询每个请求将成为一个真正的问题 - 正如 Neil Atkinson 所指出的那样,过早的优化可能会浪费时间和资源。

        但是,当计算成本成为问题时,可以考虑多种方法,具体取决于计算数据的性质。

        如果计算出的数据本身具有作为资源的优点,您可以将其保存在数据库中。例如,定期(每日、每月、每年)生成的报告需要可查询。

        否则,如果计算出的数据具有较高的“流失率”(每天都会创建许多评论),您将使用缓存来尽可能避免昂贵的查询,但将数据填充到您的数据库中可能会导致大量缓慢的 UPDATE 查询并绑定您的网络或工作进程。

        有许多相互补充的缓存方法:

        • etags 利用客户端缓存 - 如果响应没有改变,则不要重新渲染。
        • 片段缓存可避免数据库查询和重新呈现未更改数据的视图块。
        • Memcached 或 Redis 中的模型缓存可用于避免慢速查询。
        • 低级缓存可用于存储平均值等内容。

        更多详情请见Caching with Rails: An overview

        【讨论】:

          猜你喜欢
          • 2012-05-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-05
          • 2011-07-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多