トランザクション管理 Locking in mysql


トランザクション管理で色々迷ってました。mysqlの仕様を確認して、SELECT ... FOR UPDATEの理解できた!
例えば:こちらのシナリオで問題を確認して、トランザクションを直します。

シナリオ

1. try block 
// 処理「A」:↓
// 未出荷のデータを確認して、csvファイルにダウンロードする
// csvファイルデータダウンロードできたら、注文ステータス→PRE_SHIPPINGに更新する
   SELECT * FROM orders 
   WHERE status = 'NOT_SHIPPED' AND order_id IN (1,2) 
   FOR UPDATE;

2. 上記のクエリを実行する、クエリ実行でレコードがロックされる、(処理終わるまでロックされるか?)
3. まだPRE_SHIPPINGの状態です、別処理「B」で上記のレコードを更新できた!なんで?(SHIPPIED...)
4. 該当の注文関係の色々データをcsvファイルに出力するデータを作る、select ... 
5. CSVファイルをダウンロードする→DL済
6. BeginTransaction(), 処理「A」で注文情報UPDATE処理する(データの整合性大丈夫?)
7. commit
8. catch block

上記のシナリオでなんでロッグされたレコード更新されたか、データの整合性の問題が発生しました。

Note

「Locking reads are only possible when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0.」

注記↓
SELECT FOR UPDATE を使用した更新対象の行のロックは、
START TRANSACTION でトランザクションを開始するか、autocommit を 0 に設定することで、自動コミットが無効になっている場合にのみ適用されます。自動コミットが有効になっている場合は、指定に一致する行がロックされません。

参考:Locking Reads(MySql5.6)
https://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html

  • DBのautocommitを確認する、以下のクエリを実行してみました。
SHOW VARIABLES WHERE Variable_name='autocommit';
|Variable_name | Value|
|autocommit    | ON   |

上記の処理「A」と「B」の実行シナリオにBeginTransactionの場所を直します。

シナリオ(トランザクション管理修正後)

  • try block
  • BeginTransaction()
// 処理「A」
// 未出荷のデータを確認して、csvファイルにダウンロードする
// csvファイルデータダウンロードできたら、注文ステータス→PRE_SHIPPINGに更新する
   SELECT * FROM orders 
   WHERE status = 'NOT_SHIPPED' AND order_id IN (1,2) 
   FOR UPDATE;
  • 上記のクエリを実行する、クエリ実行でレコードがロックされる、(トランザクション終わるまで別処理で更新できない状態になった)
  • この間、別処理「B」で上記のレコードを更新する、更新できないです!(処理待ちの状態になる)
  • 該当の注文関係の色々データをcsvファイルに出力するデータを作る、select ... 
  • CSVファイルをダウンロードする →DL済
  • 処理「A」で注文情報UPDATE処理する(データの整合性 OK!)
  • commit  処理「B」ここで実行される、クエリの条件にヒットしないかも、(処理Aで色々更新できた)
  • catch block

Thank you for reading!