SlideShare a Scribd company logo
1 of 29
Download to read offline
外部キー制約に伴う
ロックの小話
2015/2/13 「外部キー Night」
@ichirin2501(いちりんちゃん)
1
はじめに
2
検証環境
・MySQL
5.5.28
・ストレージエンジン
InnoDB
・トランザクション分離レベル
REPEATABLE-READ, READ-COMMITTED
!
時間の都合上インデックスの話は割愛
スライドの内容
ロックおさらい

・共有と排他ロックについて

・共有と排他ロックの順序によるデッドロック例
外部キー制約に伴うロックの挙動について

・基本的なロックのかかり方

・シャドーロックの紹介(注意)

・シャドーロックによる外部キー制約時の影響
3
ロックおさらい(簡易)
• 共有ロック(LOCK_S)

共有ロック同士は互いにブロックしない

例:SELECT LOCK IN SHARE MODE
• 排他ロック(LOCK_X)

何も受け付けないぞ、排他

例:INSERT(成功), UPDATE, DELETE,

 SELECT FOR UPDATE
X S
X Conflict Conflict
S Conflict Compatible
4
大きく分けてロックは2種類
5
> BEGIN;
> SELECT * FROM player

 WHERE id = 100

 LOCK IN SHARE MODE;
> BEGIN;
トランザクションA トランザクションB
共有と排他順によるデッドロック例
6
> BEGIN;
> SELECT * FROM player

 WHERE id = 100

 LOCK IN SHARE MODE;
> BEGIN;
> SELECT * FROM player
WHERE id = 100 FOR UPDATE;
Player.id(100)に

共有ロックが取られてるため、

待たされる
トランザクションA トランザクションB
共有と排他順によるデッドロック例
待たされる
7
> BEGIN;
> SELECT * FROM player

 WHERE id = 100

 LOCK IN SHARE MODE;
> BEGIN;
> SELECT * FROM player
WHERE id = 100 FOR UPDATE;
> SELECT * FROM player
WHERE id = 100 FOR UPDATE;
DeadLock !!! Player.id(100)に

共有ロックが取られてるため、

待たされる排他->共有ロックなら
デッドロックにならない
トランザクションA トランザクションB
共有と排他順によるデッドロック例
待たされる
外部キー制約によるロック
(基本編)
8
簡単に検証
1. 外部キー制約の共有ロックを確認
2. 共有->排他順によるデッドロック例

(外部キー制約ver)
これだけは押さえておく
・INSERT時に外部キー制約される側(親)に

 共有ロックがかかる
テーブル定義の例
key Extra
id PRIMARY AUTO-INCR
player KEY-INDEX
item KEY-INDEX
key Extra
id PRIMARY AUTO-INCR
key Extra
id PRIMARY
Player
Item
PlayerItem
外部キー制約する側(子)
9
外部キー制約される側(親)
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100, 2000);
> BEGIN;
トランザクションA トランザクションB
10
外部キー制約により、

Player.id(100), Item.id(2000)

に対して共有ロックを獲得
外部キーで共有ロックがかかるのを確認
>
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100, 2000);
> BEGIN;
> SELECT * FROM player
WHERE id = 100 FOR UPDATE;
トランザクションA トランザクションB
11
外部キー制約により、

Player.id(100), Item.id(2000)

に対して共有ロックを獲得
Player.id(100)に

共有ロックが取られてるため、

待たされる
外部キーで共有ロックがかかるのを確認
>
待たされる
12
> BEGIN; > BEGIN;
> UPDATE player SET XXX = YYY
WHERE id = 100;
DeadLock !!!
共有->排他順によるデッドロック例(外部キーver)
トランザクションA トランザクションB
> INSERT INTO player_item

(player,item) VALUES(100, 2000);
> SELECT * FROM player
WHERE id = 100 FOR UPDATE;
Player.id(100)に

共有ロックが取られてるため、

待たされる
待たされる
外部キー制約によるロック
(シャドーロック編)
• シャドーロックとは

クエリが待たされたときなどでも

部分的にロックを獲得する現象のこと
• 注意:私が勝手に呼んでるロック現象です
• 外部キーに関わらず、IN, BETWEENなど

複数行ロックするようなクエリの場合にも発生

(今回は外部キー制約に伴う部分のみを紹介)
13
部分的にロックを取ってしまう原因
InnoDBのINSERTの挙動(簡易)
14
1. テーブルロック確認
2. インデックスを順番に作成
3. 外部キー制約なら共有ロック
4. 他TXからロックの影響確認と

同時にロック(uniq制限チェックなど諸々)
5. インデックス作成完了
待たされるポイント
各々の処理でロックを
確定してしまう
=> シャドーロックになる
=> インデックス定義依存
同じクエリで検証してみる
key Extra
id PRIMARY AUTO-INCR
player KEY-INDEX
item KEY-INDEX
PlayerItem
CREATE TABLE `player_item` (
…
KEY `idx_item` (`item`),
KEY `idx_player` (`player`),
…
);
15
CREATE TABLE `player_item` (
…
KEY `idx_player` (`player`),
KEY `idx_item` (`item`),
…
);
case1
case2
item,playerは外部キー
同じクエリで検証
case1: item -> player のindex順
case2: player -> item のindex順
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
トランザクションA トランザクションB
16
case1:item -> player の順でindex定義
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100,2000);
トランザクションA トランザクションB
17
• Player.id(100)に排他ロックが

取られてるため待たされる
• シャドーロックでitem.id(2000)に

対して共有ロック獲得済み
case1:item -> player の順でindex定義
待たされる
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100,2000);
トランザクションA トランザクションB
18
• Player.id(100)に排他ロックが

取られてるため待たされる
• シャドーロックでitem.id(2000)に

対して共有ロック獲得済み
case1:item -> player の順でindex定義
待たされる
> SELECT * FROM item

WHERE id = 2000 FOR UPDATE;
DeadLock !!!
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
19
case2:player -> item の順でindex定義
トランザクションA トランザクションB
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100,2000);
20
• Player.id(100)に排他ロックが

取られてるため待たされる
• item.id(2000)に対しては

共有ロックを取ってない

(取る前にPlayer.idで止まった)
case2:player -> item の順でindex定義
トランザクションA トランザクションB
待たされる
> BEGIN;
> SELECT * FROM player

WHERE id = 100 FOR UPDATE;
> BEGIN;
> INSERT INTO player_item

(player,item) VALUES(100,2000);
> SELECT * FROM item

WHERE id = 2000 FOR UPDATE;
21
>
待たされない!
• Player.id(100)に排他ロックが

取られてるため待たされる
• item.id(2000)に対しては

共有ロックを取ってない

(取る前にPlayer.idで止まった)
case2:player -> item の順でindex定義
トランザクションA トランザクションB
待たされる
補足:ロックは食いつく
22
検証
・INSERTでDuplicateEntryになったときの

 ロック獲得状況の確認
ちなみに、DuplicateEntry時など

失敗したら共有ロックになることが知られている

(成功時は排他ロック)
Uniq制限のあるテーブル定義
key Extra
id PRIMARY
token UNIQ-INDEX
item KEY-INDEX
PlayerToken
外部キー制約する側(子)
23
CREATE TABLE `player_token` (
…
PRIMARY KEY (`id`),
UNIQUE KEY `idx_token` (`token`),
KEY `idx_item` (`item`),
…
);
idがplayer.idの外部キー
itemがitem.idの外部キー
id -> token -> itemのindex順
> BEGIN;
> INSERT INTO player_token
(id,item,token) VALUES
(100,1000, ABCD );
> BEGIN;
トランザクションA
24
トランザクションB
INSERTでDuplicateEntryになったとき
>
> BEGIN;
> INSERT INTO player_token
(id,item,token) VALUES
(100,1000, ABCD );
> BEGIN;
> INSERT INTO player_token

(id,item,token) VALUES
(200,2000, ABCD );
トランザクションA
25
トランザクションB
シャドーロックでPlayer.id(200)

の共有ロックは獲得済み。

Item.id(2000)の前にtokenの

uniq制限でひっかかる
INSERTでDuplicateEntryになったとき
待たされる
>
id -> token -> itemのindex順
> BEGIN;
> INSERT INTO player_token
(id,item,token) VALUES
(100,2000, ABCD );
> BEGIN;
> INSERT INTO player_token

(id,item,token) VALUES
(200,2000, ABCD );
> COMMIT;
トランザクションA
26
トランザクションB
> (Duplicate Entry )
>
DuplicateEntryになったものの、

トランザクションが解除されたわけ

ではない。Player.id(200)の
共有ロックは獲得済み
INSERTでDuplicateEntryになったとき
id -> token -> itemのindex順
> BEGIN;
> INSERT INTO player_token
(id,item,token) VALUES
(100,2000, ABCD );
> BEGIN;
> INSERT INTO player_token

(id,item,token) VALUES
(200,2000, ABCD );
> COMMIT;
トランザクションA, A
27
トランザクションB
> BEGIN;
> (Duplicate Entry )
>
DuplicateEntryになったものの、

トランザクションが解除されたわけ

ではない。Player.id(200)の
共有ロックは獲得済み
INSERTでDuplicateEntryになったとき
> SELECT * FROM item
WHERE id = 2000 FOR UPDATE;
id -> token -> itemのindex順
> BEGIN;
> INSERT INTO player_token
(id,item,token) VALUES
(100,2000, ABCD );
> BEGIN;
> INSERT INTO player_token

(id,item,token) VALUES
(200,2000, ABCD );
> COMMIT;
トランザクションA, A
28
トランザクションB
> BEGIN;
> SELECT * FROM player
WHERE id = 200 FOR UPDATE;
> (Duplicate Entry )
>
DuplicateEntryになったものの、

トランザクションが解除されたわけ

ではない。Player.id(200)の
共有ロックは獲得済み
INSERTでDuplicateEntryになったとき
> SELECT * FROM item
WHERE id = 2000 FOR UPDATE;
待たされる
id -> token -> itemのindex順
まとめ
• 共有->排他のロック順はデッドロックの原因
• 外部キー制約があるとINSERT時に親に共有ロック
• クエリが待たされてる状態でも

部分的にロックは獲得される(シャドーロック)
• 外部キー制約の共有ロック順序はテーブル定義依存
• 外部キー制約を付けるならINSERT前に排他ロック
29

More Related Content

What's hot

オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
Moriharu Ohzu
 
Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについて
moai kids
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについて
kumake
 

What's hot (20)

ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
DDDのモデリングとは何なのか、 そしてどうコードに落とすのか
DDDのモデリングとは何なのか、 そしてどうコードに落とすのかDDDのモデリングとは何なのか、 そしてどうコードに落とすのか
DDDのモデリングとは何なのか、 そしてどうコードに落とすのか
 
Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについて
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
モジュールの凝集度・結合度・インタフェース
モジュールの凝集度・結合度・インタフェースモジュールの凝集度・結合度・インタフェース
モジュールの凝集度・結合度・インタフェース
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについて
 
分散システムについて語らせてくれ
分散システムについて語らせてくれ分散システムについて語らせてくれ
分散システムについて語らせてくれ
 
ドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみようドメイン駆動設計 ( DDD ) をやってみよう
ドメイン駆動設計 ( DDD ) をやってみよう
 
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajpAt least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
 
Where狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキーWhere狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキー
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
 
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
Mercari JPのモノリスサービスをKubernetesに移行した話 PHP Conference 2022 9/24
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?
 

外部キー制約に伴うロックの小話