SlideShare une entreprise Scribd logo
1  sur  29
Télécharger pour lire hors ligne
外部キー制約に伴う
ロックの小話
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

Contenu connexe

Tendances

MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法Tetsutaro Watanabe
 
コンテナにおけるパフォーマンス調査でハマった話
コンテナにおけるパフォーマンス調査でハマった話コンテナにおけるパフォーマンス調査でハマった話
コンテナにおけるパフォーマンス調査でハマった話Yuta Shimada
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)Koichiro Matsuoka
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話Koichiro Matsuoka
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)Takuto Wada
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意Yoshitaka Kawashima
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」Masahito Zembutsu
 
マルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのことマルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのことAmazon Web Services Japan
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかShogo Wakayama
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDeleteYu Yamada
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計Yoshinori Matsunobu
 
MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法yoku0825
 
トランザクションをSerializableにする4つの方法
トランザクションをSerializableにする4つの方法トランザクションをSerializableにする4つの方法
トランザクションをSerializableにする4つの方法Kumazaki Hiroki
 
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけRDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけRecruit Technologies
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)Yoshitaka Kawashima
 
SQLアンチパターン(インデックスショットガン)
SQLアンチパターン(インデックスショットガン)SQLアンチパターン(インデックスショットガン)
SQLアンチパターン(インデックスショットガン)Tomoaki Uchida
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 

Tendances (20)

MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法
 
コンテナにおけるパフォーマンス調査でハマった話
コンテナにおけるパフォーマンス調査でハマった話コンテナにおけるパフォーマンス調査でハマった話
コンテナにおけるパフォーマンス調査でハマった話
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」
 
マルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのことマルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのこと
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するか
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDelete
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
WiredTigerを詳しく説明
WiredTigerを詳しく説明WiredTigerを詳しく説明
WiredTigerを詳しく説明
 
MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法MySQLで論理削除と正しく付き合う方法
MySQLで論理削除と正しく付き合う方法
 
トランザクションをSerializableにする4つの方法
トランザクションをSerializableにする4つの方法トランザクションをSerializableにする4つの方法
トランザクションをSerializableにする4つの方法
 
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけRDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
 
SQLアンチパターン(インデックスショットガン)
SQLアンチパターン(インデックスショットガン)SQLアンチパターン(インデックスショットガン)
SQLアンチパターン(インデックスショットガン)
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 

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