#RailsのTransactions、Lock
##はじめに
前回のTransactions
の応用で今回はデータのロックについてです
##データのロック?
DBにおけるロックとはトランザクションが対象データにロックをかけて占有することです。
これはデータの競合を防ぐためです。
二つのトランザクションが同時に起きると、データに一貫性が持てないことがあります。
例としてはこちらのページがわかりやすかったので、参考にしてください。
##Railsでロックするには
実装方法は2つ、lock
とwith_lock
があります。
User.transaction do
user = User.lock.find(1)
user.update!(name: 'taro')
end
最初にlock
メソッドです。チェインして使います。これでトランザクション中はロックされます。処理中は**読み取りは可能です。**更新処理等は待たされます。
次に、with_lock
メソッドです。こちらはトランザクションとロックが同時にできます。
user = User.first
user.with_lock do
user.update!(name: 'taro')
end
##注意点
今回のロックの仕方は悲観的ロックと呼ばれ、ロックしないとデータは競合するだろうという推測の元するロックのやり方で、厳格なロックとなります。利用できるDBMSはPostgresかMySQLとなりますのでご注意を。
##試してみる。
参考記事でもコンソール2つ使ってデータがロックされているか試してたので確認してみます。
irb(main):028:0> user = User.first
irb(main):032:0> user.with_lock do
irb(main):033:1* sleep 30
irb(main):034:1> end
(0.1ms) begin transaction
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
~~~~~~~~~~30秒後~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(0.2ms) commit transaction
=> 30
この間に他のコンソールでuserを更新してみます。
irb(main):026:0> user.update(name: 'locking')
(0.1ms) begin transaction
SQL (0.8ms) UPDATE "users" SET "updated_at" = ?, "name" = ? WHERE "users"."id" = ? [["updated_at", "2018-12-17 13:16:12.614654"], ["name", "locking"], ["id", 1]]
(5045.1ms) commit transaction
(2.0ms) rollback transaction
ActiveRecord::StatementInvalid: SQLite3::BusyException: database is locked: commit transaction
ちゃんとロックされてます。待ち時間が長いので戻されてます。
##まとめ
データベースも学び直したくなってきた今日この頃。
##参考にしたの
Railsのデータロック
https://qiita.com/merrill/items/d9d41d64df292bd6432a
Active Record Transactions
https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
トランザクションの一貫性を保証するロック
http://www.atmarkit.co.jp/ait/articles/0212/21/news003.html
ActiveRecord::Locking::Pessimistic
https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
Railsで悲観的ロック(PostgreSQLの行レベルロック)
https://qiita.com/upinetree/items/b3329501561268f7678a