Rails 3:更新非常大的表中列的最佳方法是什么

时间:2016-12-18 07:30:46

标签: mysql ruby-on-rails ruby-on-rails-3 activerecord rake

我想更新表中包含超过220万行的所有列,其中属性设置为null。有一个Users表和一个Posts表。尽管在User中有一个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

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

修改 根据Per 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

再次感谢大家的帮助!

2 个答案:

答案 0 :(得分:1)

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,您很可能会快速耗尽服务器内存。

答案 1 :(得分:1)

您可以使用update_allsubquery来执行此操作。

sub_query = 'SELECT count(*) FROM `posts` WHERE `posts`.`user_id` = `users`.`id`'
User.where('num_posts IS NULL').update_all('num_posts = (#{sub_query})')

只需几秒钟而不是几小时。 如果是这样,您可能无需找到记录某些内容的方法。