重新计算120k记录的计数器缓存[Rails / ActiveRecord]

时间:2010-05-06 17:07:16

标签: mysql ruby-on-rails activerecord

以下情况:

我有一个poi模型,它有很多图片(1:n)。我想重新计算counter_cache列,因为值不一致。

我试图在ruby中遍历每条记录,但这需要花费太长时间,并且有时会出现一些“分段错误”错误。

所以我想知道,如果可以用原始的SQL查询来做到这一点吗?

2 个答案:

答案 0 :(得分:8)

例如,如果您拥有PostPicture型号并发布has_many :pictures,则可以使用update_all执行此操作:

Post.update_all("pictures_count=(Select count(*) from pictures where pictures.post_id=posts.id)")

答案 1 :(得分:1)

我在krautcomputing找到了一个很好的解决方案 它使用反射来查找项目的所有计数器缓存,SQL查询只查找不一致的对象,并使用Rails reset_counters来清理。

不幸的是它只适用于“常规”计数器缓存(没有类名,没有自定义计数器缓存名称)所以我对它进行了改进:

Rails.application.eager_load!

ActiveRecord::Base.descendants.each do |many_class|
  many_class.reflections.each do |name, reflection|
    if reflection.options[:counter_cache]
      one_class = reflection.class_name.constantize
      one_table, many_table = [one_class, many_class].map(&:table_name)
      # more reflections, use :inverse_of, :counter_cache etc.
      inverse_of = reflection.options[:inverse_of]
      counter_cache = reflection.options[:counter_cache]
      if counter_cache === true
        counter_cache = "#{many_table}_count"
        inverse_of ||= many_table.to_sym
      else
        inverse_of ||= counter_cache.to_s.sub(/_count$/,'').to_sym
      end
      ids = one_class
        .joins(inverse_of)
        .group("#{one_table}.id")
        .having("MAX(#{one_table}.#{counter_cache}) != COUNT(#{many_table}.id)")
        .pluck("#{one_table}.id")
      ids.each do |id|
        puts "reset #{id} on #{many_table}"
        one_class.reset_counters id, inverse_of
      end
    end
  end
end