この記事について
本記事は、2022年11月に実施した社内勉強会資料の内容をもとに、社外向けに再編集したものです。
記載の情報は執筆当時のものであり、最新の仕様やベストプラクティスとは異なる可能性があります。
実装にあたっては、必ず最新の公式ドキュメントをご確認ください。
Bulk insert(バルクインサート)とは
Bulk Insert とは、複数のレコードを 1 つのクエリでまとめて挿入する方法です。
通常、1 レコードずつ INSERT
文を発行すると、そのたびに SQL の解釈・実行のオーバーヘッドが発生します。
一方、複数行をまとめて挿入すればオーバーヘッドは 1 回で済むため、効率的かつ高速になります。
1行の挿入 INSERT INTO table_name (col_1, col_2, col_3) VALUES (1, 2, 3);
複数行の挿入 INSERT INTO table_name (col_1, col_2, col_3) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9);
railsでは
ActiveRecordのinsert_all(rails6以降)
出典:Github – 公式ドキュメント
https://github.com/rails/rails/pull/35077
出典:Railsドキュメント – insert_all
https://railsdoc.com/page/insert_all
- コールバックなし
- バリデーションなし
- UPSERTサポート(
Update Insert
) - timestamps(
created_at
,updated_at
)の設定が必要
(rails7 からはrecord_timestamps
オプションあり) gem activerecord-import
よりも速度が速い
Activerecord-Import
出典:Github – 公式リポジトリ
https://github.com/zdennis/activerecord-import
gem 'activerecord-import'
- コールバックあり
- バリデーションあり
- UPSERTサポート
- timestampsの自動設定
Activerecord-Importの実装
- カラムと配列
columns = [ :title, :author ]
values = [ ['Book1', 'George Orwell'], ['Book2', 'Bob Jones'] ]
Book.import columns, values
- ハッシュ
values = [
{ title: 'Book1', author: 'George Orwell' },
{ title: 'Book2', author: 'Bob Jones'}
]
Book.import values
- ActiveRecord Model
books = [
Book.new(title: "Book 1", author: "George Orwell"),
Book.new(title: "Book 2", author: "Bob Jones")
]
Book.import books
オプション
validate
モデルに定義したバリデーションを実行します。
ユニークネスは含みません。
Book.import books, validate: true
validate_uniqueness
ユニークネスをチェックします。
Rails のモデルで定義したものは判定していない可能性があります。
Book.import books, validate_uniqueness: true
timestamps
created_at, updated_at, created_on, updated_on に日時を自動設定するかを制御します。
books = [
Book.new(
title: "Book 1", author: "George Orwell",
created_at: "2022-11-23 10:20:30",
updated_at: "2022-11-23 10:20:30"
)
]
Book.import books, timestamps: false
on_duplicate_key_ignore
キーが重複したときにスキップするかを判定します。
on_duplicate_key_update
ユニークキー制約に引っかかった時に更新するカラムの指定を行います。
books = [Book.new(id: 1, title: "Book 1", author: "George Orwell")]
# キー重複時はタイトルのみ更新する
Book.import books, on_duplicate_key_update: [:title]
返り値
result = Book.import books
result.failed_instances
=> [#<Article id: 3, author_id: 3, title: nil, content: "", created_at: nil, updated_at: nil>]
その他
- mysqlとpostgres/sqliteで動作が異なるので注意が必要です。
- validate_uniquenessと更新処理が共存できない可能性があります。(要調査)
トランザクション処理
# 2レコード目をtitle(required)を空にしてエラーにさせる
books = [
Book.new(title: "Book1", author: "George Orwell"),
Book.new(title: "", author: "Bob Jones")
]
Book.import books
=> Book2でエラーになり、Book1も登録されない
Book.import books, batch_size: 2
=> SQLを2つに分けるとBook1は登録される
# transaction
ActiveRecord::Base.transaction do
Book.import books, batch_size: 2
end
=> transaction切ってもBook1は登録されるので、transactionは必要ない