トランザクションってなーに?mix3です
同時更新処理
スレーブ遅延のときにSELECT > UPDATEで古い情報をもとにUPDATEしてしまうと不整合が発生すると書きましたが、
たとえば同じ行を複数で同時にSELECT > UPDATEすると同じように不整合が発生することがあります。
- AがT1からSELECT
- BがT1からSELECT
- AがT1をUPDATE
- BがT1をUPDATE
このような順序で更新がかかると、BがAの更新を上書きしてしまうため、実質Aの更新が無かった事になってしまいます。
これも更新がアトミックであれば発生しないので可能であればアトミックな更新にするのが良いですが、更新が複数ある場合はそういった変更も出来ません。
そんなときこそトランザクション。そんなふうに考えていた時期が僕にもありました。
で、そういうときはトランザクションを使う物だと思っていました。トランザクションは複数の処理を一つの処理としてまとめて成功するか失敗するか、どちらかを保証してくれます。
なので、SELECT > UPDATEをトランザクションでまとめてしまえば大丈夫だよね!と思っていたので、以下のようなコードを書いて確認しました。
scoreが更新回数になるので、'v' => '<更新回数>' と揃えば不整合無く更新出来たことになるのですが、
見ての通りトランザクションを使った方は残念な結果になっています。で、すこしググッた結果トランザクションとロックは別で考えないと行けないことを知りました。
トランザクションを使ったからといって勝手に行読み込みなどをロックして整合性を保証してくれるとかでは全然無いんですね。
当たり前だろダラズ!と言われても仕方ないような勘違いですが、いやトランザクションを使わない世界でしばらく生きていたので…
で、SELECTにテーブルロックや行ロックが掛かってないためにSELECT > UPDATEの不整合が防げていないようなので、SELECT文にFOR UPDATE を付けてそれを防いでみました
結果またしても残念な結果になりました…
ちゃんとストレージエンジンを指定しましょう
結果から言うとストレージエンジンを指定していなかったのでトランザクションの使えないMyISAMでテーブルが作られていたからでした。
こんな感じでInnoDBを指定する事で期待通り、不整合なく更新されるようになりました。めでたしめでたし。
ちなみにSELECTのロックには共有ロックと排他ロックとあり、FOR UPDATEは排他ロックになります。LOCK IN SHARE MODEだと共有ロックになります。
今回の場合にLOCK IN SHARE MODEを使うと10ものプロセスが共有ロックを取り合って凄い勢いでデッドロックしました。
ロックするということは同時にデッドロックする可能性もあるということなので、適切なロックを選択して使い、デッドロックの可能性を減らしながら上手く付き合っていきたいですね。
まとめ
- トランザクションはロックとはまた別問題
- 不整合を防ぎたいならちゃんとロックのことを考えてSQLを書く
- MySQLのデフォルトストレージエンジンはMyISAMだけど、それトランザクション使えないからね?
- ロックにも種類はあり、デッドロックには気をつける
今年のブログはこれが最後な気がするので。
来年はもっとブログをコードを書いてアウトプットを増やして行きたいです。