我使用的是Rails 4.0.0和Ruby 2.0.0。我的Post
(如在博客文章中)模型与具有用户的user_name,first_name,last_name的组合的用户相关联。我希望迁移数据,以便通过外键将帖子与用户相关联,这是用户的ID。
我在posts
表中有大约1100万条记录。
我在Linux服务器上使用rake任务运行以下代码来迁移数据。但是,我的任务不断被杀死#34;由服务器,可能是由于rake任务,特别是下面的代码,消耗了太多的内存。
我发现将batch_size
降低到20并将sleep(10)
增加到sleep(60)
可以让任务运行更长时间,更新更多记录而不会被杀死,但需要明显更多的时间。
如何针对速度和内存使用情况优化此代码?
Post.where(user_id: nil).find_in_batches(batch_size: 1000) do |posts|
puts "*** Updating batch beginning with post #{posts.first.id}..."
sleep(10) # Hopefully, saving some memory usage.
posts.each do |post|
begin
user = User.find_by(user_name: post.user_name, first_name: post.first_name, last_name: post.last_name)
post.update(user_id: user.id)
rescue NoMethodError => error # user could be nil, so user.id will raise a NoMethodError
puts "No user found."
end
end
puts "*** Finished batch."
end
答案 0 :(得分:10)
Do all the work in the database which is WAY faster than moving data back and forth.
This can be accomplished with ActiveRecord. Of course PLEASE test this before you unleash it on important data.
Post
.where(user_id: nil)
.joins("inner join users on posts.user_name = users.user_name")
.update_all("posts.user_id = users.id")
Further, if posts have an index on user_id
, and users has an index on user_name
, then that will help this particular query run more quickly.
答案 1 :(得分:2)
查看AR模型上的#uncached
方法。基本上,对于请求优化,AR会在执行#find_in_batches
时缓存大量查询数据,但这会阻碍像这样的大型处理脚本。
Post.uncached do
# perform all your heavy query magic here
end
最终,如果这不起作用,请考虑使用mysql2
gem来避免AR开销,只要您不依赖于更新中的任何回调/业务逻辑。
答案 2 :(得分:2)
如果可以加入,我会选择z5h的方法。 否则,您可以向用户模型添加索引(可能在单独的迁移中),并在更新每个帖子时跳过验证,回调和填充:
add_index :users, [:user_name, :first_name, :last_name] # Speed up search queries
Post.where(user_id: nil).find_each do |post|
if user = User.find_by(user_name: post.user_name,
first_name: post.first_name,
last_name: post.last_name)
post.update_columns(user_id: user.id) # ...to skip validations and callbacks.
end
end
请注意,find_each
相当于find_in_batches
+迭代每个帖子,但可能不会更快(请参阅Active Record Query Interface上的Rails指南)
答案 3 :(得分:1)
结合其他答案,我能够连接表,并以1000行的批量更新多个列,速度降低,而且我的进程没有被服务器杀死。
我发现这种方法效果最好,尽可能将代码保存在ActiveRecord API中。
{{1}}
答案 4 :(得分:0)
添加新的临时布尔属性更新
Post.where(updated: false).find_in_batches(batch_size: 1000) do |posts|
ActiveRecord::Base.transaction do
puts "*** Updating batch beginning with post #{posts.first.id}..."
posts.each do |post|
user = User.find_by(user_name: post.user_name, first_name: post.first_name, last_name: post.last_name)
if user
post.update_columns(user_id: user.id, updated: true)
else
post.update_columns(updated: true)
end
end
puts "*** Finished batch."
end
end