最近用 padrino 作点东西,开发环境下批量插入数据,特别慢,1000 条记录大概 1 分钟。 查看 debug 信息,发现 1000 条 insert 后面,带了 1000 个 commit;
原因很明显,冗余 commit 带来的性能下降,所以,如果能实现多次 insert,一次 commit,应该能解决。
自己是 ruby 新手,也没找到个解决办法,直到看见这个贴http://ruby-china.org/topics/580 里面@yangyanhao 推荐的资料https://www.coffeepowered.net/2009/01/23/mass-inserting-data-in-rails-without-killing-your-performance/
这个完美的解决了我的问题,从原来的 1 分钟入库,变成 1 秒钟入库! debug 日志变成了 1000 个 insert 后面带一个 commit。
附修改前入库代码 CodeName.create @carry
修改后入库代码 ActiveRecord::Base.transaction do CodeName.create @carry end
可以,今天晚上有空把 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(", ")}"
在这里没有放在一个事务里面,因为仅仅是一条单一的语句,数据库会把它放在一个事务里面的。
这个时候我一般推荐改用 sequel http://sequel.rubyforge.org
sequel 提供了 import 方法 http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-import DB[:table].import([:x, :y], [[1, 2], [3, 4]])
activerecord-import
https://github.com/zdennis/activerecord-import
Benchmark
https://github.com/zdennis/activerecord-import/wiki/Benchmarks
@nouse 看到这一句话,有一种想去研究的冲动
"Sequel has restored my faith in Ruby. It's really amazing. The O/RM I've been hoping for for years." -- Sam Smoot, creator of DataMapper
谢谢推荐
最近也在做类似这样的东西,重点是批量插入的时候,要避免 Model 的 validation 和 callback,我现在都是用 sanitize_sql
来手工拼接 SQL,执行,unique 的验证通过数据库唯一索引来搞
Insert 语句可以直接容错已有记录的情况,比如:
INSERT INTO photos (user_id, file_id, lat, lng, created_at, updated_at, deleted_at) VALUES (27,'dbcb70427c4e6292d3462ca1dc65bf02d505f515',0.0,0.0,'2014-02-27 06:25:45','2014-02-27 06:25:45',null) ON DUPLICATE KEY UPDATE updated_at = values(updated_at), deleted_at = values(deleted_at)