如何在rails中批量编写查询?

时间:2017-08-04 11:41:47

标签: ruby-on-rails batch-file subquery

我有一张包含800 000条记录的users表。我在token表中创建了一个名为users的新字段。对于所有新用户令牌已填充。对于现有用户填充令牌,我使用以下代码编写了一个rake任务。我觉得这对生产环境中的这些记录不起作用。如何使用批处理或其他一些编写查询的方式重写这些查询

users = User.all
users.each do |user|
 user.token = SecureRandom.urlsafe_base64(nil, false)
 user.save
end

4 个答案:

答案 0 :(得分:1)

How you want to proceed depends on different factors: is validation important for you when executing this? Is time an issue? If you don't care about validations, you may generate raw SQL queries for each user and then execute them at once, otherwise you have options like ActiveRecord transactions:

User.transaction do
  users = User.all
  users.each do |user|
    user.update(token: SecureRandom.urlsafe_base64(nil, false))
  end
end

This would be quicker than your rake task, but still would take some time, depending on the number of users you want to update at once.

答案 1 :(得分:1)

lower_limit = User.first.id
upper_limit = 30000
while true

  users = User.where('id >= ? and  id< ?',lower_limit,upper_limit)
  break if users.empty?
  users.each do |user|
    user.update(token: SecureRandom.urlsafe_base64(nil, false))
  end
  lower_limit+=30000
  upper_limit+=30000
end

答案 2 :(得分:1)

我认为最好的选择是使用find_eachtransactions

doc for find_each:

  

循环遍历数据库中的一组记录(例如,使用ActiveRecord :: Scoping :: Named :: ClassMethods #all方法)是非常低效的,因为它会尝试一次实例化所有对象。

     

在这种情况下,批处理方法允许您批量处理记录,从而大大减少内存消耗。

     

find_each方法使用批量大小为1000的find_in_batches(或者由:batch_size选项指定)。

交易文件:

  

事务是保护性块,如果SQL语句只能成为一个原子操作

,那么它们只是永久性的

如果你关心内存,因为你带来了内存中的所有800k用户,User.all.each将实例化消耗大量内存的800k对象,所以我的方法将是:

User.find_each(batch_size: 500) do |user|
  user.token = SecureRandom.urlsafe_base64(nil, false)
  user.save
end

在这种情况下,它只实例化500个用户而不是1000个默认batch_size

如果您仍希望仅在数据库的一个事务中执行此操作,则可以使用@ {Francesco的answer

答案 3 :(得分:-1)

常见错误是无需实例化模型实例。虽然AR实例化并不便宜。 你可以试试这个天真的代码:

BATCH_SIZE = 1000
while true
  uids = User.where( token: nil ).limit( BATCH_SIZE ).pluck( :id )
  break if uids.empty?
  ApplicationRecord.transaction do
    uids.each do |uid|
      # def urlsafe_base64(n=nil, padding=false)
      User
        .where( id: uid )
        .update_all( token: SecureRandom.urlsafe_base64 )
    end
  end
end

下一个选项是对SecureRandom.urlsafe_base64使用本机DB的模拟,并运行一个查询,如:

UPDATE users SET token=db_specific_urlsafe_base64 WHERE token IS NULL

如果找不到模拟,可以从预先计算的CSV文件(id,token = SecureRandom.urlsafe_base64)预填充临时表(如PostgreSQL''COPY命令) 并运行一个查询,如:

UPDATE users SET token=temp_table.token
FROM temp_table
WHERE (users.token IS NULL) AND (users.id=temp_table.id)

但实际上,由于以下原因,您无需填写token现有用户

  

我在rails中使用“令牌”进行基于令牌的身份验证 - John

您必须检查用户的令牌是否为NULL(或已过期)并将其重定向到登录表单。这是常用的方式,它可以节省您的时间。