SlideShare a Scribd company logo
なかったらINSERTしたい
し、あるならロック取りたい
やん?
第2回 DevOps勉強会
ichirin2501
1
似たネタで乗っかることにしました
2
想定する状況(MySQL)
• ユーザーアクションでINSERTしたいんだよね
• 既にデータがあるなら排他制御しつつ参照して

処理したいんだよね
=> 罠がある(InnoDB, REPEATABLE-READ)
なぜだめなのか、に焦点をあてて話したいと思います
用意したコードはてきとーなので注意
3
よくある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)
VALUES (1);
ブロックされる
id=2,3,4も同様にブロック

id=7とかはブロックされない
7
ギャップロック同士は

ブロックしない
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
ちなみに
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でもロックを取るので大量発行してると

ロック待ちで詰む
11
共有 -> 排他ロック
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
だったらIGNORE?
$dbh->do( BEGIN );
$res = $dbh->do( INSERT IGNORE );
if (!$res) {
$row = $dbh->do( SELECT FOR UPDATE );
};
マサカリ投げていく所存
無視しても共有ロックは取られる
13
よくある3 - ロックなし参照
$dbh->do( BEGIN );
$row = $dbh->do( SELECT );
if (! $row) {
$dbh->do( INSERT );
} else {
$row = $dbh->do( SELECT FOR UPDATE );
}
14
あまりよくない理由
• 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 DUPLICATE KEY UPDATEで
無意味な更新をするのがミソ

(name = name みたいな
17
解決されること
• INSERTでもUPDATEでも排他ロックになるため、

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

ロックと仲良くしよう!
19

More Related Content

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