Your SlideShare is downloading. ×

外部キー制約に伴う小話
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

外部キー制約に伴う小話

62
views

Published on

mysql,innodb,foreign key

mysql,innodb,foreign key

Published in: Engineering

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
62
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
4
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 外部キー制約に伴う ロックの小話 2015/2/13 「外部キー Night」 @ichirin2501(いちりんちゃん) 1
  • 2. はじめに 2 検証環境 ・MySQL 5.5.28 ・ストレージエンジン InnoDB ・トランザクション分離レベル REPEATABLE-READ, READ-COMMITTED ! 時間の都合上インデックスの話は割愛
  • 3. スライドの内容 ロックおさらい
 ・共有と排他ロックについて
 ・共有と排他ロックの順序によるデッドロック例 外部キー制約に伴うロックの挙動について
 ・基本的なロックのかかり方
 ・シャドーロックの紹介(注意)
 ・シャドーロックによる外部キー制約時の影響 3
  • 4. ロックおさらい(簡易) • 共有ロック(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. 5 > BEGIN; > SELECT * FROM player
  WHERE id = 100
  LOCK IN SHARE MODE; > BEGIN; トランザクションA トランザクションB 共有と排他順によるデッドロック例
  • 6. 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. 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. 外部キー制約によるロック (基本編) 8 簡単に検証 1. 外部キー制約の共有ロックを確認 2. 共有->排他順によるデッドロック例
 (外部キー制約ver) これだけは押さえておく ・INSERT時に外部キー制約される側(親)に
  共有ロックがかかる
  • 9. テーブル定義の例 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 外部キー制約される側(親)
  • 10. > BEGIN; > INSERT INTO player_item
 (player,item) VALUES(100, 2000); > BEGIN; トランザクションA トランザクションB 10 外部キー制約により、
 Player.id(100), Item.id(2000)
 に対して共有ロックを獲得 外部キーで共有ロックがかかるのを確認 >
  • 11. > 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. 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)に
 共有ロックが取られてるため、
 待たされる 待たされる
  • 13. 外部キー制約によるロック (シャドーロック編) • シャドーロックとは
 クエリが待たされたときなどでも
 部分的にロックを獲得する現象のこと • 注意:私が勝手に呼んでるロック現象です • 外部キーに関わらず、IN, BETWEENなど
 複数行ロックするようなクエリの場合にも発生
 (今回は外部キー制約に伴う部分のみを紹介) 13
  • 14. 部分的にロックを取ってしまう原因 InnoDBのINSERTの挙動(簡易) 14 1. テーブルロック確認 2. インデックスを順番に作成 3. 外部キー制約なら共有ロック 4. 他TXからロックの影響確認と
 同時にロック(uniq制限チェックなど諸々) 5. インデックス作成完了 待たされるポイント 各々の処理でロックを 確定してしまう => シャドーロックになる => インデックス定義依存
  • 15. 同じクエリで検証してみる 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順
  • 16. > BEGIN; > SELECT * FROM player
 WHERE id = 100 FOR UPDATE; > BEGIN; トランザクションA トランザクションB 16 case1:item -> player の順でindex定義
  • 17. > 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定義 待たされる
  • 18. > 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 !!!
  • 19. > BEGIN; > SELECT * FROM player
 WHERE id = 100 FOR UPDATE; > BEGIN; 19 case2:player -> item の順でindex定義 トランザクションA トランザクションB
  • 20. > 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 待たされる
  • 21. > 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. 補足:ロックは食いつく 22 検証 ・INSERTでDuplicateEntryになったときの
  ロック獲得状況の確認 ちなみに、DuplicateEntry時など
 失敗したら共有ロックになることが知られている
 (成功時は排他ロック)
  • 23. 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順
  • 24. > BEGIN; > INSERT INTO player_token (id,item,token) VALUES (100,1000, ABCD ); > BEGIN; トランザクションA 24 トランザクションB INSERTでDuplicateEntryになったとき >
  • 25. > 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順
  • 26. > 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順
  • 27. > 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順
  • 28. > 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順
  • 29. まとめ • 共有->排他のロック順はデッドロックの原因 • 外部キー制約があるとINSERT時に親に共有ロック • クエリが待たされてる状態でも
 部分的にロックは獲得される(シャドーロック) • 外部キー制約の共有ロック順序はテーブル定義依存 • 外部キー制約を付けるならINSERT前に排他ロック 29