Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

なかったらINSERTしたいし、あるならロック取りたいやん?

Plus De Contenu Connexe

なかったらINSERTしたいし、あるならロック取りたいやん?

  1. 1. なかったらINSERTしたい し、あるならロック取りたい やん? 第2回 DevOps勉強会 ichirin2501 1
  2. 2. 似たネタで乗っかることにしました 2
  3. 3. 想定する状況(MySQL) • ユーザーアクションでINSERTしたいんだよね • 既にデータがあるなら排他制御しつつ参照して
 処理したいんだよね => 罠がある(InnoDB, REPEATABLE-READ) なぜだめなのか、に焦点をあてて話したいと思います 用意したコードはてきとーなので注意 3
  4. 4. よくある1 $dbh->do( BEGIN ); $row = $dbh->do( SELECT FOR UPDATE ); if (! $row) { $dbh->do( INSERT ); } 4
  5. 5. だめな理由 • 空打ちロックはギャップロックになる • ギャップロックされた空間のINSERTは
 全てブロックされる • そしてギャップロック同士はブロックされない 5
  6. 6. ギャップロックって? インデックスの 間をロックすること 5 6 10 id (pk) [-inf, 5) [7, 10) [11, +inf] 6
  7. 7. ギャップロックされた
 空間のINSERTは止まる 5 6 10 id (pk) tx A tx B SELECT * FROM t 
 WHERE id = 4 FOR UPDATE BEGIN BEGIN INSERT INTO t (id) VALUES (1); ブロックされる id=2,3,4も同様にブロック
 id=7とかはブロックされない 7
  8. 8. ギャップロック同士は
 ブロックしない 5 6 10 id (pk) tx A tx B SELECT * FROM t 
 WHERE id = 4 FOR UPDATE BEGIN BEGIN SELECT * FROM t 
 WHERE id = 3 FOR UPDATE INSERT INTO t (id) VALUES(4) INSERT INTO t (id) VALUES(3) 同じギャップ空間だけど止まらない Deadlock Error 8
  9. 9. ちなみに 5 6 10 id (pk) tx A tx B BEGIN BEGIN INSERT INTO t (id) VALUES(4) INSERT INTO t (id) VALUES(3) これはデッドロックにならない 9
  10. 10. よくある2 - とりあえず挿入 $dbh->do( BEGIN ); $row; try { $dbh->do( INSERT ); } catch { $row = $dbh->do( SELECT FOR UPDATE ); }; 10
  11. 11. だめな理由 • INSERTでDuplicate-Entryになったら、
 共有ロックになる • 共有 -> 排他ロックはデッドロックの原因となり、
 Dup -> FOR-UPDATEの 間に刺さる • Dupでもロックを取るので大量発行してると
 ロック待ちで詰む 11
  12. 12. 共有 -> 排他ロック 5 6 10 id (pk) tx A tx B SELECT * FROM t 
 WHERE id = 5 FOR UPDATE BEGIN BEGIN INSERT INTO t (id) VALUES (5) SELECT * FROM t 
 WHERE id = 5 FOR UPDATE Deadlock Error Err: Duplicate Entry… 12
  13. 13. だったらIGNORE? $dbh->do( BEGIN ); $res = $dbh->do( INSERT IGNORE ); if (!$res) { $row = $dbh->do( SELECT FOR UPDATE ); }; マサカリ投げていく所存 無視しても共有ロックは取られる 13
  14. 14. よくある3 - ロックなし参照 $dbh->do( BEGIN ); $row = $dbh->do( SELECT ); if (! $row) { $dbh->do( INSERT ); } else { $row = $dbh->do( SELECT FOR UPDATE ); } 14
  15. 15. あまりよくない理由 • SELECTからINSERTまでの 間でDuplicateEntry • 最初のSELECT文で全体のスナップショットが取ら れるため、排他制御としては不十分な状態になる 15
  16. 16. 面倒だけど無難な解決案 • Duplicate-EntryになったらRollbackして
 リトライする 16
  17. 17. バッドノウハウのご紹介 $dbh->do( BEGIN ); $dbh->do( INSERT ON DUPLICATE KEY UPDATE ); $row = $dbh->do( SELECT FOR UPDATE ); ON DUPLICATE KEY UPDATEで 無意味な更新をするのがミソ
 (name = name みたいな 17
  18. 18. 解決されること • INSERTでもUPDATEでも排他ロックになるため、
 共有 -> 排他ロックのデッドロックが発生しない • トランザクション開始直後に打てばスナップショッ トによるバグ埋め込みが発生しない 18
  19. 19. そんな感じで、
 ロックと仲良くしよう! 19

×