🖥

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

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

https://github.com/YumaInaura/YumaInaura/issues/2927

チャットメンバー募集

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

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ

Twitter

https://twitter.com/YumaInaura

公開日時

2020-01-10

Discussion