Bulk insert(バルクインサート)

この記事について

本記事は、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は必要ない

 

 

役に立ったらシェアしていただけると嬉しいです
  • URLをコピーしました!
  • URLをコピーしました!
目次