Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした