LoginSignup
1
1

More than 5 years have passed since last update.

トランザクション分離レベルをrepeatable_read(MySQLのデフォルト)にしてロストアップデートを体験してみる

Last updated at Posted at 2016-06-28

ロストアップデート発生!!

トランザクション分離レベルをrepeatable_read(MySQLのデフォルト)にしてロストアップデートを発生させてみる。

ワーカー

class HogeWorker
  @queue = :hoge

  def self.perform
    ActiveRecord::Base.transaction(isolation: :repeatable_read) do
      h = Hoge.find_by(id: 1)
      sleep(3.0)
      h.count = h.count + 1
      h.save!
    end
  rescue => e
    # ここを通ることはなかった
    Resque.logger.info('ERRORRRRRRRRR s')
    Resque.logger.info(e.class)
    Resque.logger.info(e)
    Resque.logger.info('ERRORRRRRRRRR e')
  end
end

ワーカー呼び出し

100.times do
  Resque.enqueue(HogeWorker)
end

ワーカー起動

ワーカーが1つだと綺麗に1つずつ処理されるので、ロストアップデートが起こらなかった。
なので、ワーカーは5つ立ち上げた。

VVERBOSE=1 bundle exec env rake resque:workers QUEUE='*' COUNT='5'

結果
countカラム0から始まった場合、本当は100になっているところ、ロストアップデートが発生したので、21になっていた。

具体的なSQLとしては、下記のように+1した結果が19になっているUPDATE文が何回も発行されていた(同様にして18(などの他の数字も)のものも複数発行されている)。

UPDATE `hoges` SET `count` = 19, `updated_at` = '2016-06-28 07:09:22' WHERE `hoges`.`id` = 1

ロストアップデートが発生しないように、SELECT FOR UPDATEでロックしてみる

find_byのところで、ロックさせてみる。

c = Hoge.find_by(id: 1).lock!

このようにしたとき、count0から始めたとき結果は100になっていた。
おそらく、SELECT FOR UPDATEがうまく機能した例といえると思う。

ちなみに、トランザクション分離レベルで最も最強の(まったく制約を緩めていない)serializableを指定してみると...

class HogeWorker
  @queue = :hoge

  def self.perform
    ActiveRecord::Base.transaction(isolation: :serializable) do
      h = Hoge.find_by(id: 1)
      sleep(3.0)
      h.count = h.count + 1
      h.save!
    end
  rescue => e
    # ここを通ることはなかった
    Resque.logger.info('ERRORRRRRRRRR s')
    Resque.logger.info(e.class)
    Resque.logger.info(e)
    Resque.logger.info('ERRORRRRRRRRR e')
  end
end

このように制約をまったく緩めない最強レベルのserializableを指定した場合は、
ActiveRecord::StatementInvalidという例外が発生する。

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE 以下略

といったメッセージもログに添えられていた。

ちなみに、serializableの場合は lockしてもlockしてないときと同じく例外発生した

serializableHoge.find_by(id: 1).lock!としてみたが、
ActiveRecord::StatementInvalidとなりMysql2::Error: Deadlockが複数発生した。

1
1
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
1
1