可以,今天晚上有空把 https://www.coffeepowered.net/2009/01/23/mass-inserting-data-in-rails-without-killing-your-performance/ 这篇文章核心地方翻译了一下
Rails AR 并没有很好的支持大数据量的插入,因为它并没有做什么额外处理的工作,你可以会说“只是循环创建了大量的 AR 对象,没有什么的”
是这样工作的,如果效率是我们关心的因素,那这样肯定就不是最好的选择了。
AR 提供了非常容易操作 DB 的接口,但是它并不是最快的操作。
实例化一个 AR 对象的代价是昂贵的,如果你实例化了很多 AR 对象,会加快垃圾收集器运行,这将显著影响性能。有几个方案,但是取决于你需要速度到底有多快。
第一种:使用 transactions
Instead of
1000.times { Model.create(options) }
You want:
ActiveRecord::Base.transaction do
1000.times { Model.create(options) }
end
数据库执行所有的插入是在一个 BEGIN....COMMIT, 而不是对于每一个执行都是 BEGIN....COMMIT, 从来节省了 (90.1ms) COMMIT 的时间。
第二种:Get down and dirty with the raw SQL
如果你知道你的数据是有效的并且可以跳过验证,你可以通过直接生成 SQL 从而节省大量时间。
想象一下,例如,你正在运行如下 code
1000.times {|i| Foo.create(:counter => i) }
这将产生 1000 个 AR 对象并运行验证和生成 insert sql 语句然后插入到数据库中。你能意识到通过直接生成 sql 将提升大量性能。
1000.times do |i|
Foo.connection.execute "INSERT INTO foos (counter) values (#{i})"
end
如果插入的值没有过滤,你有必须对输入的值使用 sanitize_sql 方法过滤,但是通过直接拼接 sql 你能意识到能提高大量性能。当然把这些 insert 语句放在一个 BEGIN....COMMIT 中,如第 1 种方案那样,会提高更高的性能。
Foo.transaction do
1000.times do |i|
Foo.connection.execute "INSERT INTO foos (counter) values (#{i})"
end
end
第三种:大量数据一次性插入
很多数据库都支持通过单一 sql 语句插入大量数据。如果你习惯使用它,这将是到目前为止最快的方式。
inserts = []
TIMES.times do
inserts.push "(3.0, '2009-01-23 20:21:13', 2, 1)"
end
sql = "INSERT INTO user_node_scores (score
, updated_at
, node_id
, user_id
) VALUES #{inserts.join(", ")}"
在这里没有放在一个事务里面,因为仅仅是一条单一的语句,数据库会把它放在一个事务里面的。