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.

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

14,652 views

Published on

mysql,innodb,foreign key

Published in: Engineering
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

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

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

×