What is this?
一般的なトランザクションと排他制御に関するまとめとそれをRailsで実現するためのメモ書き
Assumption
- RDBSにおける話
- Railsを使う前提
トランザクションについて
> ITの分野では、取引記録などの意味の他に、ソフトウェアの処理方式の一つで、互いに関連・依存する複数の処理をまとめ、一体不可分の処理単位として扱うことを指す場合が多い。
要するに、相互に関係する処理をこれ以上分割できない一つのまとまりとして捉えたもの
例)銀行口座の送金処理(出金と入金処理が不可分一体となっている)
トランザクションにおいてはACID特性が保たれる必要がある
ACID特性について
atomicity:原子性
トランザクションにおける処理が全て実行されるか、実行されないかのどちらかになる性質
例)送金に成功すれば出金・入金共に行われ、失敗すればどちらの処理も行われない
consistancy:一貫性
トランザクションの前後でデータに不整合が起こらない性質
例)送金トランザクションで残高が負の数になるなど
isolation:独立性
トランザクション処理が他のトランザクションに影響を与えない性質
ここで排他制御を行う
durability:耐久性
トランザクション完了時にその結果が記録され失われない性質
排他制御について
- 代表的な例
今回取り扱うのはロックに限ります。
- 代表的なロック
- 楽観的ロック
- 悲観的ロック
- 共有ロック
- 排他ロック
今回は、特によく使われる(印象の)悲観ロックと楽観ロックについてまとめます。(Active Recordに備わっているロック機構もこの二種類なので)
悲観ロックと楽観ロックについて
悲観ロック
- 前提:他者が同時更新は頻繁に起きる
- 方法:SELECT 〜 FOR UPDATEなど
- デメリット:ロックの解放漏れがあるとツラい(多分最近のFWはこの辺りもよしなにやってくれているので、意識しなくてもいいことが多いような気がする)
- 備考:一般的に採用される。ECサイトのセールような決まった時間に同時多発的にアクセスが集中するような場合が典型的な気がする。
楽観ロック
Railsにおける排他制御
楽観ロック
- テーブルに
lock_version
という名前のinteger型カラムを作成する - Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やす
- 更新リクエストが発生したときのlock_versionの値がデータベース上のlock_versionカラムの値よりも小さい場合、更新リクエストは失敗し、ActiveRecord::StaleObjectErrorエラーが発生する(例外処理が必要)
- ActiveRecord::Base.lock_optimistically = falseを設定するとこの動作をオフにできる
- ActiveRecord::Baseには、lock_versionカラム名を上書きするためのlocking_column属性が用意されている
悲観ロック
- リレーションの構築時にlockを使うと、選択した行に対する排他的ロックを取得できる
- lockを用いているリレーションは、デッドロック(互いの処理がロックされて処理が進まなくなった状態)条件を回避するために通常トランザクションの内側にラップされる
例 Book.transaction do book = Book.lock.first book.title = 'Algorithms, second edition' book.save! end
Mysql SQL (0.2ms) BEGIN Book Load (0.3ms) SELECT * FROM `books` LIMIT 1 FOR UPDATE Book Update (0.4ms) UPDATE `books` SET `updated_at` = '2009-02-07 18:05:56', `title` = 'Algorithms, second edition' WHERE `id` = 1 SQL (0.8ms) COMMIT
SELECT 〜 FOR UPDATEが内部的に走ってるんですね
Railsガイドには他のロックを実装する方法も乗っているので折に触れてそちらも確認して行こうと思います。