経緯
業務でプルリクエストレビューをした際に、既存レコードが存在する場合は一件ずつアップデート、新規作成する場合はバルクインサートする、という実装に遭遇した。アップデートもまとめて出来んのかな?と思ったから調べてみた。
bulk insert, bulk update
単一SQL で 複数件同時 に保存、更新する方法。
メリット
複数SQL で 複数件 実行するより
単一SQL で 複数件 実行することで
一般的に大量件数のInsert/Update処理を高速化できる。
バルクインサート処理 (BULK INSERT:FORALL 構文) は 1 回のコンテキストスイッチで大量のレコードを処理(バルク処理)することができる。 このコンテキストスイッチ(PL/SQL エンジンと SQL エンジンの間で発生する処理のやり取り) の回数を減らすことによって大量データの処理時のレスポンスを向上することが可能になる。
デメリット
調査中
MySQLでINSERTのデッドロックに嵌る人を1人でも減らすために
検証環境
Rubyバージョン
$ ruby -v
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin17]
Railsバージョン
$ bundle exec rails -v
Rails 5.0.0.1
Gem
Gemfile.rb
# ActiveRecord import
gem 'activerecord-import', '0.20.2'
TableColumn
table |
---|
id |
user_id |
flag_1 |
flag_2 |
... |
created_at |
updated_at |
models/class_name.rb
validates :user_id, uniqueness: true
状況
- formから複数のuser_id(usersテーブルのid)を送信
- Parameters:
{ user_ids: '*, **, ***, ...' }
- Parameters:
- 既存レコードはupdate
バルクインサート&バルクアップデート ロジック
logic.
instance_object_array = []
params[:user_ids].split(',').each do |user_id|
object = ClassName.find_or_initialize_by(user_id: user_id)
object.attributes = { flag_1: true, flag_2: false }
instance_object_array << object
end
ClassName.import instance_object_array, on_duplicate_key_update: [:flag_1, :flag_2]
Duplicate Key Update
MySQL, PostgreSQL (9.5+), and SQLite (3.24.0+) support on duplicate key update (also known as "upsert") which allows you to specify fields whose values should be updated if a primary or unique key constraint is violated.