【问题标题】:Rails 3: What is the best way to update a column in a very large tableRails 3:更新超大表中的列的最佳方法是什么
【发布时间】:2016-12-18 07:30:45
【问题描述】:

我想更新具有超过 220 万行且属性设置为空的表中的所有列。有一个用户表和一个帖子表。尽管 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

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

编辑: 根据 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

再次感谢大家的帮助!

【问题讨论】:

  • 快速谷歌搜索github.com/ondrejbartas/rake-progressbar。它很旧,但值得一试。
  • find_each 在这样的情况下会更好,否则进程可能会因超时而被终止并且查询结果会得到更好的处理 - 要检查进度,您可以直接查询数据库(或使用 GUI ) 计算剩余结果

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


【解决方案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,您可能会很快耗尽服务器内存。

【讨论】:

  • 您可以使用 .update_all 来执行此操作,而不是作为带有子选择的单个 SQL 查询。我已经用 Postgres 做到了,但不确定如何用 MySQL 做到这一点。它可能会将运行时间减少到几秒钟或几分钟。
  • 非常感谢。我的问题现在解决了。此外,我重命名了问题并对其进行了编辑,以便将来对其他人更有用。
【解决方案2】:

您可以使用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})')

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多