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

  • 325
    いいね
  • 6
    コメント
この記事は最終更新日から1年以上が経過しています。

あるテーブルに対して複数のレコードを同時に登録したいことがあります。
このときに登録する数だけ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で処理しましょう!