0
0

More than 1 year has passed since last update.

#Rails + #MySQL / Transaction と INSERT RECORD と ROLLBACK で排他制御の実験

Last updated at Posted at 2020-01-10

パターン1 - プロセスA

INSERTのあと、しばらく後続の処理が続くが、最後には失敗するケース
Transaction内の処理が失敗してロールバックする

ActiveRecord::Base.transaction { User.create!(unique_id: 'X1'); sleep 15; raise; }
# (0.7ms)  BEGIN
# User Create (0.9ms)  INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('X1', '2020-01-09 08:18:34')
#
# ... wait ...
#
#  (10.4ms)  ROLLBACK
# from (pry):14:in `block in <main>'

パターン1 - プロセスB

プロセスAのTransaction内での処理と重複するレコードをINSERTする
プロセスAの処理が失敗するのを待ってからコミットされ、さらに後続の処理が開始・成功する

ActiveRecord::Base.transaction { User.create!(unique_id: 'X1'); puts 'OK!'; }
# 
# (0.5ms)  BEGIN
#
# ... wait ...
#
# User Create (2573.1ms)  INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('X1', '2020-01-09 08:18:41')
# OK!
#  (9.4ms)  COMMIT
# => nil

パターン2 - プロセスA

INSERTのあと、しばらく後続の処理が続いて、最後に成功するケース

ActiveRecord::Base.transaction { User.create!(unique_id: 'X2'); sleep 15; puts 'OK!'; }
# (1.0ms)  BEGIN
# User Create (2.2ms)  INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('X2', '2020-01-09 08:22:14')
#
# ... wait ...
#
# OK!
#  (19.2ms)  COMMIT
# => nil

パターン2 - プロセスB

プロセスAの処理が成功するまで INSERT は待ち受け状態になる
プロセスAの処理が成功してTransactionが終了すると、こちらのINSERTはユニーク制限に反することが確定するため、そのタイミングで処理が失敗する
INSERT より後続の処理は実行されない

ActiveRecord::Base.transaction { User.create!(unique_id: 'X2'); puts 'OK!'; }
#
# ... wait ...
#
#    (0.6ms)  BEGIN
# User Create (6831.2ms)  INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('X2', '2020-01-09 08:22:22')
# (5.2ms)  ROLLBACK
# ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry 'X2' for key 'index_users_on_unique_id': INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('X2', '2020-01-09 08:22:22')

パターン3 - プロセスA

他のパターンと同じく、INSERTの後にしばらくTransaction内の処理が続く

ActiveRecord::Base.transaction { User.create!(unique_id: 'X3'); sleep 15; puts 'OK!'; }

パターン3 - プロセスB

プロセスAのTransaction内での処理とは重複しないレコードをINSERTする
ユニーク制限に弾かれない登録なので、すぐコミットされる

ActiveRecord::Base.transaction { User.create!(unique_id: 'Y1'); puts 'OK!'; }
# (0.8ms)  BEGIN
# User Create (0.9ms)  INSERT INTO `users` (`unique_id`, `created_at`) VALUES ('Y1', '2020-01-09 08:18:38')
# OK!
#  (2.9ms)  COMMIT
# => nil

Original by Github issue

チャットメンバー募集

何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

Twitter

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0