【问题标题】:Rails counter_cache for Model.count without any association, in order to make SELECT COUNT (*) fasterRails counter_cache for Model.count 没有任何关联,以使 SELECT COUNT (*) 更快
【发布时间】:2011-05-12 01:04:47
【问题描述】:
我正在使用 Model.count 对我的一个模型中的行进行总计,并且有点担心性能,因为最终,这个模型会变得非常大,因此 SELECT COUNT (*) 非常慢。
有没有办法在没有:belongs_to 关系的情况下使用counter_cache?或者另一种计算行数的性能友好的方式?我考虑过制作另一个模型,只是我存储这样的计算但不确定这是最好的方法。
【问题讨论】:
标签:
ruby-on-rails
ruby-on-rails-3
ruby-on-rails-5
【解决方案1】:
比制作Cache 模型更简单的是只使用Rails.cache。
Rails.cache.read("elephant_count") #=> nil
Rails.cache.write("elephant_count", 1) #=> true
Rails.cache.read("elephant_count") #=> 1
Rails 默认使用文件存储 (tmp/cache)。
然后您可以将 Rails.cache.write 增量和减量放入模型的 after_create 和 after_destroy 挂钩中,并通过调用 Rails.cache.read 覆盖 Model.size。
您可以在 Rails 首次初始化时初始化缓存,方法是在 config/initializers 中放置一个名为 initialize_cache.rb 的文件,其中包含:
Rails.cache.write('elephant_count', 0) if Rails.cache.read('elephant_count').nil?
【解决方案2】:
如果您想要维护一个计数器,无论是使用counter_cache 还是手动执行,Rails 都会使用回调来维护您的计数器,这将在创建/销毁新后代时增加/减少计数器。
我不知道在不使用belongs_to 关系的情况下存储counter_cache 的方法,因为只有父级可以存储子级的计数。
称重性能
如果您的表将变得“大”,请使用大量行填充您的测试数据库,然后使用EXPLAIN 开始运行一些 SQL 查询以获得数据库查询的性能。看看使用counter_cache 进行记录创建/销毁时的性能影响是否会被您首先需要访问这些计数器的频率所抵消。
如果计数器不需要始终 100% 准确,您可以改为使用 cron 作业或后台工作程序定期更新缓存。
总结:
- 仅当您需要这些计数器足以抵消创建/销毁记录所花费的稍长时间时,才应使用 counter_cache。
- 据我所知,使用
counter_cache 与使用回调的手动替代方法相比,不太可能对性能造成很大损害。
- 如果缓存不需要准确,请利用这一点并减少执行计算的频率。
【解决方案4】:
像这样定义CachedCount 关注点怎么样?
module CachedCount
extend ActiveSupport::Concern
included do
after_create :increment_cached_count
after_destroy :decrement_cached_count
end
class_methods do
def count
return cached_count if cached_count
Rails.cache.write(cached_count_key, super)
Rails.cache.read(cached_count_key) || super # fallback because in some Rails env. Rails.cache may not be available
end
def cached_count_key
"#{model_name.collection}_count"
end
def cached_count
Rails.cache.read(cached_count_key)
end
end
def increment_cached_count
return self.class.count unless self.class.cached_count
Rails.cache.write(self.class.cached_count_key, self.class.cached_count + 1)
end
def decrement_cached_count
return self.class.count unless self.class.cached_count
Rails.cache.write(self.class.cached_count_key, self.class.cached_count - 1)
end
end
然后你将它包含在你的众多模型中:
class MyNumerousModel
include CachedCount
# [...]
end
现在,每次调用 MyNumerousModel.count 时,实际上都是在调用关注点中的类方法。当您创建或销毁 MyNumerousModel 的一个实例时,after_create 和 after_destroy 回调会负责更新 MyNumerousModel.cached_count。