如何在Rails 3/4中批量运行更新?

Mot*_*ars 31 sql ruby-on-rails

我需要批量更新数千条记录,我想分批处理更新.首先,我试过:

Foo.where(bar: 'bar').find_in_batches.update_all(bar: 'baz')
Run Code Online (Sandbox Code Playgroud)

...我希望生成SQL,例如:

"UPDATE foo SET bar = 'baz' where bar='bar' AND id > (whatever id is passed in by find_in_batches)"
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为find_in_batches返回一个数组,而update_all需要一个ActiveRecord关系.

这是我接下来尝试的:

Foo.where(bar: 'bar').select('id').find_in_batches do |foos|
  ids = foos.map(&:id)
  Foo.where(id: ids).update_all(bar: 'baz')
end
Run Code Online (Sandbox Code Playgroud)

这是有效的,但它显然运行一个选择后跟更新,而不是基于我的'where'条件的单个更新.有没有办法清理它,以便选择和更新不必是单独的查询?

dla*_*kty 59

在Rails 5中,有一种新的方便方法ActiveRecord::Relation#in_batches来解决这个问题:

Foo.in_batches.update_all(bar: 'baz')
Run Code Online (Sandbox Code Playgroud)

检查文档以获取详细信


pdo*_*obb 11

我也很惊讶,没有一种更简单的方法可以做到这一点......但我确实提出了这种方法:

batch_size = 1000
0.step(Foo.count, batch_size).each do |offset|
  Foo.where(bar: 'bar').order(:id)
                       .offset(offset)
                       .limit(batch_size)
                       .update_all(bar: 'baz')
end
Run Code Online (Sandbox Code Playgroud)

基本上这将:

  1. 在每次之间创建一个偏移数组0Foo.count逐步调整batch_size.例如,如果Foo.count == 10500你得到:[0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
  2. 循环遍历这些数字并在SQL查询中将它们用作OFFSET,确保按顺序排序id,并限制为batch_size.
  3. 更新大多数batch_size"索引"大于的记录offset.

这基本上是在生成的SQL中执行您所说的希望的手动方式.太糟糕了,它不能仅仅通过标准库方法以这种方式完成...虽然我确信你可以创建自己的一个.


Fai*_*sal 7

这是迟了2年,但这里的答案是:a)对于大型数据集来说非常慢; b)忽略内置轨道功能(http://api.rubyonrails.org/classes/ActiveRecord/Batches.html).

随着偏移值的增加,它将根据您的数据库服务器进行序列扫描,直到它到达您的块,然后提取数据进行处理.随着您的偏移量达到数百万,这将非常缓慢.

使用"find_each"迭代器方法:

Foo.where(a: b).find_each do |bar|
   bar.x = y
   bar.save
end
Run Code Online (Sandbox Code Playgroud)

这具有每次保存运行模型回调的额外好处.如果您不关心回调,请尝试:

Foo.where(a: b).find_in_batches do |array_of_foo|
  ids = array_of_foo.collect &:id
  Foo.where(id: ids).update_all(x: y)
end
Run Code Online (Sandbox Code Playgroud)

  • 不需要`ids = array_of_foo.collect &:id`。您可以将对象数组传递到 `where` 子句中,如下所示: `Foo.where(id: array_of_foo).update_all(x: y)` (2认同)