Chapter 05

MySQLのロック機構(SELECT FOR UPDATE)

TKD
TKD
2022.03.27に更新

この章は何

  • MySQLのSELECT FOR UPDATEの挙動について理解するための章

SELECT FOR UPDATEとは

  • レコードに対して排他ロックをかけるための処理
    • 他のトランザクションからSELECT FOR UPDATEUPDATEDELETEをできなくする
    • ただし単なるSELECTは可能

SELECT FOR UPDATEを使う上での注意点

  • トランザクションの中で実行する必要がある(トランザクション内じゃないとただのSELECTになってしまう)
  • 処理が終わったら早めにCOMMITROLLBACKを行う(行ロックを長時間専有すると他の処理ができないため)

実際に手元で挙動を確認してみる

事前に用意するテーブル

  • bank_accounts(銀行口座)
    • balance(残高), integer

試すこと

  • 他のトランザクションからSELECT FOR UPDATEできないこと
  • 他のトランザクションからUPDATEできないこと
  • 他のトランザクションから単なるSELECTはできること
  • トランザクション内でSELECT FOR UPDATEしないとただのSELECTになること

他のトランザクションからSELECT FOR UPDATEできないこと

  1. 左右両方のターミナルでBEGINしてトランザクションを開始する

  2. 左ターミナルでSELECT FOR UPDATEを実行する(行ロックをかける)

  3. 右ターミナルでSELECT FOR UPDATEを実行すると処理が待たされる(行ロックがかかっているため参照できない)

    他のトランザクションでSELECT FOR UPDATEしようとすると処理が待たされる

  4. 左ターミナルでCOMMITすると右ターミナルでSELECT FOR UPDATEの処理が動く(左ターミナルが行ロックを解放したため)

    行ロックが解放されると待たされていた処理が実行される

他のトランザクションからUPDATEできないこと

  1. 左右両方のターミナルでBEGINしてトランザクションを開始する

  2. 左ターミナルでSELECT FOR UPDATEを実行する(行ロックをかける)

  3. 右ターミナルでUPDATEを実行すると処理が待たされる(行ロックがかかっているため)

    他のトランザクションでUPDATEしようとすると処理が待たされる

  4. 左ターミナルでCOMMITすると右ターミナルでUPDATEの処理が動く(左ターミナルが行ロックを解放したため)

    行ロックが解放されると待たされていた処理が実行される

他のトランザクションから単なるSELECTはできること

  1. 左右両方のターミナルでBEGINしてトランザクションを開始する
  2. 左ターミナルでSELECT FOR UPDATEを実行する(行ロックをかける)
  3. 右ターミナルでSELECTを実行すると行を取得できる

    行ロックがかかっていても単なるSELECTなら他のトランザクションからも実行できる

トランザクション内でSELECT FOR UPDATEしないとただのSELECTになること

  1. 左ターミナルでBEGINせずにSELECT FOR UPDATEを実行する

    BEGINせずにSELECT FOR UPDATEを実行する

  2. 続いて右ターミナルでSELECT FOR UPDATEを実行すると取得できてしまう(つまり左ターミナルで実行したSELECT FOR UPDATEで行ロックがかかっていない)

    他のトランザクションからSELECT FOR UPDATEを実行できてしまう

例に関する補足

上記の例はトランザクション分離レベルがREPEATABLE READの場合の挙動なので、別のトランザクション分離レベルが設定されている場合は挙動が少し異なります。

とはいえMySQLのトランザクション分離レベルはデフォルトでREPEATABLE READになっており変更することもあまりないので、トランザクション分離レベルによる挙動の違いはそこまで意識する必要はないと思います。

まとめ

  • SELECT FOR UPDATEはレコードに対して排他ロックをかけるための処理
    • 他のトランザクションからSELECT FOR UPDATEUPDATEDELETEができなくなる
    • ただし単なるSELECTは可能
  • SELECT FOR UPDATEはトランザクションの中で実行する必要がある
  • 処理が終わったら早めにCOMMITROLLBACKでロックを解放した方がよい