我想更新具有超过220万行的属性设置为null的表中的所有列。有一个用户表和一个帖子表。即使“用户”中有num_posts列,但只有大约70,000个用户填充了该数字;否则我必须像这样查询数据库:

@num_posts = @user.posts.count


我想使用迁移来更新属性,但是我不确定这是否是最佳方法。这是我的迁移文件:

class UpdateNilPostCountInUsers < ActiveRecord::Migration
  def up
    nil_count = User.select(:id).where("num_posts IS NULL")

    nil_count.each do |user|
      user.update_attribute :num_posts, user.posts.count
    end
  end

  def down
  end
end


在控制台中,我对num_posts为null的前10行进行查询,然后为每个user.posts.count使用puts。 10行总时间为85.3毫秒,平均为8.53毫秒。 8.53ms * 220万行大约是5.25小时,并且没有更新任何属性。我怎么知道我的迁移是否按预期运行?有没有办法登录到控制台%complete?我真的不想等待5个多小时才能发现它什么也没做。非常感激。

编辑:
根据下面的Max Max的评论,我放弃了迁移路线,并使用find_each批量解决了问题。我通过在User模型中编写以下代码解决了该问题,该代码是我从Rails控制台成功运行的:

def self.update_post_count
    nil_count = User.select(:id).where("num_posts IS NULL")
    nil_count.find_each { |user|
        user.update_column(:num_posts, user.posts.count) if user.posts
    }
end


再次感谢大家的帮助!

最佳答案

desc 'Update User post cache counter'
task :update_cache_counter => :environment do

  users = User.joins('LEFT OUTER JOIN "posts" ON "posts.user_id" = "users.id"')
              .select('"users.id", "posts.id", COUNT("posts.id") AS "p_count"')
              .where('"num_posts" IS NULL')

  puts "Updating user post counts:"
  users.find_each do |user|
    print '.'
    user.update_attribute(:num_posts, user.p_count)
  end
end


首先,请不要使用迁移来完成本质上的维护任务。迁移主要应更改数据库的架构。尤其是在这种情况下长时间运行时,并且可能会中途失败,从而导致迁移失败和数据库状态出现问题。

然后,您需要解决以下事实:调用user.posts会导致N + 1查询,而应该加入posts表并选择一个计数。

而且,如果不使用batches,则可能会很快耗尽服务器的内存。

关于mysql - Rails 3:在超大表中更新列的最佳方法是什么,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41206335/

10-12 03:53
查看更多