Rails 3:在非常大的数据库中更新列的最佳方法是什么 table
Rails 3: What is the best way to update a column in a very large table
我想更新 table 中超过 220 万行的所有列,其中属性设置为空。有一个用户 table 和一个帖子 table。即使用户中有一列 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.53 毫秒*220 万行大约是 5.25 小时,并且没有更新任何属性。我如何知道我的迁移是否如预期的那样 运行?有没有办法登录到控制台 %complete?我真的不想等 5 个多小时才发现它什么也没做。非常感谢。
编辑:
根据下面Max的评论,我放弃了迁移路线并使用find_each批量解决问题。我通过在用户模型中编写以下代码解决了这个问题,我从 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 查询这一事实,您应该加入帖子 table 和 select 计数。
如果不使用 batches,您可能会很快耗尽服务器内存。
您可以使用 update_all
和 subquery
来执行此操作。
sub_query = 'SELECT count(*) FROM `posts` WHERE `posts`.`user_id` = `users`.`id`'
User.where('num_posts IS NULL').update_all('num_posts = (#{sub_query})')
只需几秒钟,而不是几小时。
如果是这样,您可能不必找到记录内容的方法。
我想更新 table 中超过 220 万行的所有列,其中属性设置为空。有一个用户 table 和一个帖子 table。即使用户中有一列 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.53 毫秒*220 万行大约是 5.25 小时,并且没有更新任何属性。我如何知道我的迁移是否如预期的那样 运行?有没有办法登录到控制台 %complete?我真的不想等 5 个多小时才发现它什么也没做。非常感谢。
编辑: 根据下面Max的评论,我放弃了迁移路线并使用find_each批量解决问题。我通过在用户模型中编写以下代码解决了这个问题,我从 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 查询这一事实,您应该加入帖子 table 和 select 计数。
如果不使用 batches,您可能会很快耗尽服务器内存。
您可以使用 update_all
和 subquery
来执行此操作。
sub_query = 'SELECT count(*) FROM `posts` WHERE `posts`.`user_id` = `users`.`id`'
User.where('num_posts IS NULL').update_all('num_posts = (#{sub_query})')
只需几秒钟,而不是几小时。 如果是这样,您可能不必找到记录内容的方法。