Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
なかったらINSERTしたい
し、あるならロック取りたい
やん?
第2回 DevOps勉強会
ichirin2501
1
似たネタで乗っかることにしました
2
想定する状況(MySQL)
• ユーザーアクションでINSERTしたいんだよね
• 既にデータがあるなら排他制御しつつ参照して

処理したいんだよね
=> 罠がある(InnoDB, REPEATABLE-READ)
なぜだめなのか、に焦点をあて...
よくある1
$dbh->do( BEGIN );
$row = $dbh->do( SELECT FOR UPDATE );
if (! $row) {
$dbh->do( INSERT );
}
4
だめな理由
• 空打ちロックはギャップロックになる
• ギャップロックされた空間のINSERTは

全てブロックされる
• そしてギャップロック同士はブロックされない
5
ギャップロックって?
インデックスの 間をロックすること
5
6
10
id (pk)
[-inf, 5)
[7, 10)
[11, +inf]
6
ギャップロックされた

空間のINSERTは止まる
5
6
10
id (pk)
tx A tx B
SELECT * FROM t 

WHERE id = 4 FOR UPDATE
BEGIN BEGIN
INSERT INTO t (id...
ギャップロック同士は

ブロックしない
5
6
10
id (pk)
tx A tx B
SELECT * FROM t 

WHERE id = 4 FOR UPDATE
BEGIN BEGIN
SELECT * FROM t 

WHERE...
ちなみに
5
6
10
id (pk)
tx A tx B
BEGIN BEGIN
INSERT INTO t (id) VALUES(4)
INSERT INTO t (id) VALUES(3)
これはデッドロックにならない
9
よくある2 - とりあえず挿入
$dbh->do( BEGIN );
$row;
try {
$dbh->do( INSERT );
} catch {
$row = $dbh->do( SELECT FOR UPDATE );
};
10
だめな理由
• INSERTでDuplicate-Entryになったら、

共有ロックになる
• 共有 -> 排他ロックはデッドロックの原因となり、

Dup -> FOR-UPDATEの 間に刺さる
• Dupでもロックを取るので大量発行して...
共有 -> 排他ロック
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)
S...
だったらIGNORE?
$dbh->do( BEGIN );
$res = $dbh->do( INSERT IGNORE );
if (!$res) {
$row = $dbh->do( SELECT FOR UPDATE );
};
マサカ...
よくある3 - ロックなし参照
$dbh->do( BEGIN );
$row = $dbh->do( SELECT );
if (! $row) {
$dbh->do( INSERT );
} else {
$row = $dbh->do( ...
あまりよくない理由
• SELECTからINSERTまでの 間でDuplicateEntry
• 最初のSELECT文で全体のスナップショットが取ら
れるため、排他制御としては不十分な状態になる
15
面倒だけど無難な解決案
• Duplicate-EntryになったらRollbackして

リトライする
16
バッドノウハウのご紹介
$dbh->do( BEGIN );
$dbh->do( INSERT ON DUPLICATE KEY UPDATE );
$row = $dbh->do( SELECT FOR UPDATE );
ON DUPLIC...
解決されること
• INSERTでもUPDATEでも排他ロックになるため、

共有 -> 排他ロックのデッドロックが発生しない
• トランザクション開始直後に打てばスナップショッ
トによるバグ埋め込みが発生しない
18
そんな感じで、

ロックと仲良くしよう!
19
Upcoming SlideShare
Loading in …5
×

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

25,148 views

Published on

題の問題に対して、InnoDBのバッドノウハウ紹介

Published in: Engineering
  • Be the first to comment

なかったら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

×