29
MySQLで遭遇したトランザク ションとロックのお話take2 小林拓

My sqlで遭遇したトランザクションとロックのお話take2 2

Embed Size (px)

Citation preview

MySQLで遭遇したトランザクションとロックのお話take2

小林拓

トランザクション

排他制御(ロック)

楽観ロック

悲観ロック

前提知識

トランザクション処理は、既知の一貫した状態のデータベースを維持するよう設計されており、相互依存のある複数の操作が全て完了するか、全てキャンセルされることを保証する。(Wikipediaより)

トランザクション

トランザクションの例

•G減少•所持数増加

購入

トランザクションの例

•G減少•所持数増加

購入

エラー

なかったことにしたい!!

トランザクションの例

•G減少•所持数増加

購入

セット

全て反映⇒commit

全て元に戻す⇒rollback

or

トランザクション実装

•SQLBEGIN

~

COMMIT 又は ROLLBACK

•RailsActiveRecord::Base.transaction do

~

end

排他制御(ロック)

参考 : http://www.slideshare.net/kuromoyo/20140717-37115076

基本構成

DBサーバー

アプリケーションサーバー

悲観ロックと楽観ロック

楽観ロック

Rollback

悲観ロック

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

ロックをかけ忘れると

http://blogos.com/article/36121/

gree ドリランド事件

↓記事

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だ!!

READ UNCOMMITED

READ COMMITED

READ COMMITED

こうなると思ってました

REPEATABLE READ

こうなりました

教訓

必ずtransactionの外で一度select文を走らせてデータをとるようにしましょう!!

REPEATABLE READではトランザクション

が開始された時点で参照される値は同じになる

REPEATABLE READ

SERIALLIZABLE

複数トランザクションのSQLが入り混じら

ないように、強制的にトランザクションを順序付けて処理します。

今まで出てきたすべての問題は発生しませんが、この分離レベルは読み取るすべての行にロックをかけます。

ちなみに