LoginSignup
533
499

More than 5 years have passed since last update.

ActiveRecordで複数レコード、BULK INSERTする方法とパフォーマンスについて

Last updated at Posted at 2013-02-13

あるテーブルに対して複数のレコードを同時に登録したいことがあります。
このときに登録する数だけINSERTを発行するのはあまり効率がよくありません。
そこでBULK INSERTと言われる単発のSQLで一括登録する方法がよく使われます。

BULK INSERTとは

単純なbooksというテーブルを使い、BULK INSERTはどういうものか確認する。

books.sql
CREATE TABLE `books` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

複数のINSERTを発行する。

insert.sql
INSERT INTO books (columns) VALUES (values);
INSERT INTO books (columns) VALUES (values);
INSERT INTO books (columns) VALUES (values);

BULK INSERTで一括登録する。

bulkinsert.sql
INSERT INTO books (columns) VALUES (values), (values), (values);

それぞれのSQLでこのような違いがありますが、登録されるレコード結果は同じになります。

RailsでBULK INSERTを使う

RailsのActiveRecordでBULK INSERTを使うには、activerecord-importという大変便利なgemがあるので利用させていただきます。
activerecord-import

使い方はとても簡単で、登録するレコード(model)のインスタンスをすべて配列にいれて、クラスメソッドのimportにそれを渡すだけです。

activerecord-import.rb
books = []
10.times do |i| 
  books << Book.new(:name => "book #{i}")
end
Book.import books

これで10回分のINSERT文が1つにまとめられます。

パフォーマンスを検証する

1度に登録する数を100レコードにし、それを100回繰り返す処理で計測してみる。

Railsの通常処理でINSERTを繰り返すパターン。

insert.rb
100.times do |i|
    100.times do |_i|
      Book.create! :name => "book_#{i}_#{_i}"
    end
 end
insert.sql
BEGIN;
INSERT INTO books (...) VALUES (...);
COMMIT;
BEGIN;
INSERT INTO books (...) VALUES (...);
COMMIT;
BEGIN;
INSERT INTO books (...) VALUES (...);
COMMIT;
...

activerecord-importを使って、BULK INSERTするパターン。

bulkinsert.rb
100.times do |i|
  books = []
  100.times do |_i|
    books << Book.new(:name => "book_#{i}_#{_i}")
  end
  Book.import books
end
bulkinsert.sql
INSERT INTO `books` (...) VALUES (...), (...), (...), ...;

Railsの通常パターンでは毎回COMMITされてしまうので、TRANSACTIONを使ったパターンも追加する。

transaction.rb
100.times do |i|
  ActiveRecord::Base.transaction do
    100.times do |_i|
      Book.create! :name => "book_#{i}_#{_i}"
    end
  end
end
transaction.sql
BEGIN
INSERT INTO books (...) VALUES (...);
INSERT INTO books (...) VALUES (...);
INSERT INTO books (...) VALUES (...);
...
COMMIT

計測結果

Insert BulkInsert Transaction
10.6876sec 4.0554sec 7.1316sec

それぞれ7回計測し、1番速いものと遅いものを除外した5回平均のデータ。
実行環境 MacBookPro, Core i7 2.9GHz, SSD256GB, Mem8GB

単純なテーブルでの比較ですが、BULK INSERTを使うとパフォーマンスは大きく向上しました。
複数レコードを登録するときは、ぜひBULK INSERTで処理しましょう!

533
499
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
533
499