Download - My sqlで遭遇したトランザクションとロックのお話take2 2
トランザクション処理は、既知の一貫した状態のデータベースを維持するよう設計されており、相互依存のある複数の操作が全て完了するか、全てキャンセルされることを保証する。(Wikipediaより)
トランザクション
排他制御(ロック)
参考 : http://www.slideshare.net/kuromoyo/20140717-37115076
Id lock_version
1 0
楽観ロックの実装
•Railsの場合usersテーブル
lock_versionというカラムを追加する
楽観ロックが適用されるときActiveRecord::StaleObjectError というExceptionが発生してRollbackする
※楽観ロックの実装は本来処理上で手動で実装させるもの
楽観ロックある時とない時の違い
UPDATE `users` SET `money` = 4, `updated_at` = '2015-02-25 11:07:28', `lock_version` = 3 WHERE (`users`.`id` = 2 AND `users`.`lock_version` = 2)
楽観ロックがないときのSQL
楽観ロックがあるときのSQL
UPDATE `users` SET `money` = 4, `updated_at` = '2015-02-25 11:07:28’ WHERE (`users`.`id` = 2)
悲観ロックの実装
•Railsの場合
•SQL
BEGINSELECT `users` WHERE (`users`.`id` = 2) FOR UPDATE…COMMIT または ROLLBACK
user = User.find_by!(id: 2) (あらかじめインスタンスを取得しておく)
ActiveRecord::Base.transaction douser.lock!
end
READ UNCOMMITED
READ COMMITED
REPEATABLE READ
SERIALLIZABLE
トランザクション分離レベル
<= Oracle, PostgreSQL, SQL Severのデフォルト
<= MySQLのデフォルト
参考: http://d.hatena.ne.jp/fat47/20140212/1392171784
下に行くほど不都合な読み込み現象が発生しなくなるが、パフォーマンスが落ちる
Id money
1 400
例(Rails+MySQL)
User.transaction douser = User.find_by(id: 1)user.money += 400user.save!
end
BEGIN;SELECT * FROM users WHERE id = 1;UPDATE users SET users.money = 800 WHERE id = 1;COMMIT;
usersテーブル
SQLをだす
例リクエスト リクエスト
BEGIN;
SELECT * FROM users WHERE id = 1;UPDATE users SET users.money = 800 WHERE id = 1;COMMIT;
BEGIN;
SELECT * FROM users WHERE id = 1;UPDATE users SET users.money = ? WHERE id = 1;COMMIT
このとき、?に入る値はいくつでしょう?
ほぼ同時にリクエストが飛んできた。このとき実行されたSQLは以下の通り
例について考察
答えは800でした
片方のトランザクションで既にcommitしてあるから、SELECT * FROM users WHERE id = 1;これで取得できる値はusers.money = 800なはず。だから800 + 400 = 1200。よって?の値は1200だ!!