2016-12-18 7 views
0

属性がnullに設定されている220万行以上のテーブルを持つすべての列を更新したいとします。 UsersテーブルとPostsテーブルがあります。 Userにはnum_postsの列がありますが、その数には約70,000人のユーザーしか存在しません。そう私はそうのようなDBを照会する必要があります:私は属性を更新するために、移行を使用したいと私はそれを行うための最善の方法だかどうかはわからないRails 3:非常に大きなテーブルの列を更新する最適な方法は何ですか

@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ためプットを使用しました。合計時間は8.53msの平均で10行で85.3msでした。 8.53ms * 2.2百万行は約5.25時間で、それはどんな属性も更新していない。移行が期待通りに実行されているかどうかを確認するにはどうすればよいですか? %completeにコンソールにログする方法はありますか?私は実際に何もしなかったことを知るために5時間以上待つことを本当に望んでいません。とても有難い。

編集: 以下のMaxのコメントで、私は移行ルートを断念し、find_eachを使用して問題をバッチで解決しました。

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 

おかげで再び助けみんなのために:私は、私が正常にRailsのコンソールから実行したユーザーモデル、次のコードを記述することによって、問題を解決しました!

+0

すばやくGoogleがhttps://github.com/ondrejbartas/rake-progressbarを表示します。それは古いですが、試してみる価値があります。 – David

+0

[find_each](http://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each)は、このような状況で優れています。そうしないと、プロセスがタイムアウトによって強制終了され、クエリ結果が処理されますより良い - 進捗状況を確認するには、単純にDBに直接問い合わせるか(またはGUIを使用して)残りの結果を数えることができます – Mat

答えて

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クエリが発生し、代わりに投稿テーブルに参加してカウントを選択する必要があることにご注意ください。

batchesを使用しないと、サーバーのメモリがすぐに使い果たされる可能性があります。

+0

サブセレクトを持つ単一のSQLクエリとして代わりに '.update_all'を使ってこれを行うことができます。私はPostgresでやったことがありますが、MySQLでどうやってそれを行うのかは不明です。実行時間を数秒または数分に短縮する可能性があります。 – max

+0

ありがとうございます。私の問題は解決されました。さらに、私はその質問の名前を変更し、将来他の人にも役立つように編集しました。 – Jay

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})') 

時間ではなく数秒で完了します。 もしそうなら、何かを記録する方法を見つける必要がないかもしれません。

関連する問題