デッドロックについて

10371 ワード

デッドロックの分析を続けます:前の分析の多くは表面的で、概念的です.この記事は、プロジェクトで実際に発生したデッドロックの問題に基づいて、さらに分析します.
分析する前に、いくつかの問題を明らかにします.
MVCC:
MySQLのInnoDBでは、マルチバージョンベースの同時制御プロトコル(MVCC):読み込みと書き込みが競合しない.
MVCCでの読み取り操作は2つに分けられます.
スナップショットの読み取り:ロックなしで現在のバージョン(履歴バージョンの可能性があります)を記録します.簡単なselect文はスナップショット読み取りに属し、ロックされていません:select*from test where?
現在の読み取り:最新バージョンを読み込んでいますが、なぜ、この場合の読み取り記録にはロックがかかっているため、他の記録はこの記録を変更する機会がありません.特殊な読み取り:select*from test where?lock in share mode;select * from table where ? for update;insert/update/delete/;いずれも現在の読み取りに属します.
MySQL/InnoDBで定義された4つの独立性レベル:
Read Uncommited:コミットされていないレコードを読み込むことができます.このレベルは使用されません.
Read Committed(RC):読み取りに対して記録ロック(記録ロック)をかけ、幻読み現象がある.
Repeatable Read(RR):読み取りに対してロック(レコードロック)をかけるとともに、読み取り記録の範囲をロックし、新たな記録が範囲内にあるものは挿入できず、幻読みは存在しない.
Serializable:MVCC同時制御からロックベース同時制御に劣化する.すべての読み取り操作は、現在の読み取り、読み取りロック(Sロック)、書き込みロック(Xロック)である.
意向ロック:トランザクションは、SロックとXロックを要求する前に、対応するIS、IXロックを取得する必要があります.
意向共有ロック(IS):トランザクションは、テーブル内の数行の共有ロックを取得します.
意向排他ロック(IX):トランザクションは、テーブル内の数行の排他ロックを取得します.
意向ロックは、テーブル全体のクエリー以外のリクエストをブロックしません.トランザクションAとトランザクションBは、同じ数行のデータのISとIXを同時に取得することができる.
行ロック:
レコード・ロック(Record Locks):インデックス・レコードのローのみをロックします.単一のインデックスレコードにロックを追加すると、レコード自体ではなくインデックスがロックされます.
ギャップロック(Gap Locks):インデックス区間をロックします.インデックスレコード間のギャップにロックを追加するか、インデックスレコードの前または後にロックを追加します.インデックス自体は含まれません.
next-keyロック(Next-Key Locks):record lock+gap lock、左開右閉区間.innodbのデフォルトでは、このロックを使用してレコードをロックします.ただし、クエリのインデックスに一意の属性が含まれている場合、Next-Key Lockは最適化され、Record Lockに降格します.つまり、インデックス自体のみがロックされ、範囲ではありません.
挿入意向ロック(Insert Intention Locks):Gap Locksにはinsert時に発生する挿入意向ロックが存在する.複数のトランザクションが同じインデックスギャップに異なるデータを同時に書き込む場合、他のトランザクションの完了を待つ必要はなく、ロック待機は発生しません.
レコードインデックスにキー値4と7が含まれていると仮定し、異なるトランザクションはそれぞれ5と6を挿入し、各トランザクションは4-7の間に追加された挿入意向ロックを生成し、挿入行の排他ロックを取得しますが、データ行が衝突しないため、互いにロックされません.
オートロック(AUTO-INCLocks):AUTO_に関連する特殊な表レベルロックINCREMENT列のトランザクション挿入操作時に発生します.
ロー・ロックの互換性マトリクス:
[img]http://dl2.iteye.com/upload/attachment/0123/9612/52f4b70e-b2a1-3153-a075-96967ee3db3e.png[/img]
表注:横方向はすでに保持されているロックで、縦方向は要求されているロックです.
一:delete文のロックメカニズム:
インデックスなしでの削除アクション+RR:
delete from t1 where id = 10;idにはインデックスがなく、全テーブルスキャンしかできません.すべてのレコードにXロックが付けられ、各レコード間の隙間にもGAPロックが付けられます.
二:insert文のロックメカニズム:

INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.

簡単なinsertはinsertの行に対応するインデックスレコードに列ロックを追加します.これはrecord lockで、gapはありません.そのため、他のsessionがgapギャップにレコードを挿入するのをブロックすることはありません.
Insertの前に、insertion intention gap lock、すなわち意向gapロックというロックも追加されます.この意向gapロックの役割は、複数のトランザクションが同じgapギャップを同時に挿入する場合、挿入されたレコードがgapギャップの同じ位置でない限り、他のsessionを待つ必要がなく、insert操作に本当のgap lockを加える必要がないことを示すことです.同時トランザクションは、同じgapに意向gapロックを追加できます.
単一キー競合エラー(duplicate-key error)が発生したと仮定すると、重複するインデックスレコードに共有ロックが追加されます.この共有ロックは、同じレコードに共有ロックを追加する2つの同時insertがあるなど、同時ロックの場合にデッドロックが発生します.このとき、このレコードは他のトランザクションに排他ロックをかけられました.このトランザクションがコミットまたはロールバックされると.2つの同時insert操作はデッドロックが発生する(TaはTbが共有ロックを解放するのを待ってから下へ行くことができ、TbはTaが共有ロックを解放するのを待ってから下へ行くことができる).
Insertで使うのはLOCK_X排他錠|LOCK_GAPクリアランスロック|LOCK_INSERT_INTENTION挿入意向ロック挿入の隙間をチェック、このモードでLOCK_S共有ロック|LOCK_GAPクリアランスロックのロックモードが衝突し、LOCK_X排他錠|LOCK_GAPクリアランスロックのロックモードが衝突します.しかし同じギャップGAPに対しては2つのロックモードがLOCK_X排他錠|LOCK_GAPクリアランスロック|LOCK_INSERT_INTENTION挿入意向ロックは互換性があります.
Insertデッドロックシーン解析:以下のシーンは[url]を参照しています.http://yeshaoting.cn/article/database/mysql%20insert%E9%94%81%E6%9C%BA%E5%88%B6/[/url]
1.duplicate key errorによるデッドロック
これは主に2つ以上のトランザクションが同時に一意のキーと同じレコード挿入操作を行う場合に発生します.
[img]http://dl2.iteye.com/upload/attachment/0123/9793/fa86d04d-7c67-3da7-a7e2-5df93f3c24ed.png[/img]
T 1がcommitの場合、T 2とT 3は唯一のキー競合を報告します:ERROR 1062(23000):Duplicate entry‘6’for key‘PRIMARY’
T 1がrollbackの場合、T 3はDeadlockを報告します.
このオフライン操作後の結果は以上のシナリオと一致する.
デッドロック成因
1.トランザクションT 1は、レコードの挿入に成功し、インデックスid=6の排他的レコードロック(LOCK_X|LOCK_REC_NOT_GAP)を取得する.
2、トランザクションT 2、T 3に続いてもレコードの挿入が開始され、意図ロックの挿入が要求される(LOCK_X|LOCK_GAP|LOCK_INSERT_INTENTION).ただし、一意キーの重複競合が発生したため、それぞれ要求された排他的挿入意向ロック(LOCK_X|LOCK_GAP|LOCK_INSERT_INTENTION)は、共有記録ロック(LOCK_S|LOCK_REC_NOT_GAP)に移行する.
3,T 1は、インデックスid=6上の排他的記録ロック(LOCK_X|LOCK_REC_NOT_GAP)をロールバックし、T 2およびT 3は、インデックスid=6上の排他的記録ロック(LOCK_X|LOCK_REC_NOT_GAP)を要求する.
XロックはSロックと反発するため、T 2とT 3はいずれも相手がSロックを解除するのを待つ.
デッドロックが発生しました!
2,GAPとInsert Intentionの衝突によるデッドロック
テーブル構造:

CREATE TABLE `t` (
`a` int(11) NOT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`),
KEY `idx_b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

データ:

mysql> select * from t;
+----+------+
| a | b |
+----+------+
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 11 | 22 |
+----+------+

[img]http://dl2.iteye.com/upload/attachment/0123/9799/b5bee694-0576-39e5-945a-a16a53f973d7.png[/img]
デッドロック成因
1,トランザクションT 1はクエリ文を実行し,インデックスb=6にNext-keyロック(LOCK_X|LOCK_ORDINARY)を付けるとidx_bインデックス範囲(4,22).
2,トランザクションT 2はクエリ文を実行し,インデックスb=8にNext-keyロック(LOCK_X|LOCK_ORDINARY)を付けるとidx_bインデックス範囲(4,22).要求されたGAPは既に保有しているGAPと互換性があるため、トランザクションT 2はidx_bインデックス範囲(4,22)もロックに成功した.
3,トランザクションT 1は挿入文を実行し,まず他のInsert Intentionロックを追加する.要求されたInsert Intentionロックが既存のGAPロックと互換性がないため、トランザクションT 1は、T 2がGAPロックを解放するのを待つ.
4,トランザクションT 2は挿入文を実行し,T 1がGAPロックを解放するのを待つ.
デッドロックが発生しました.
プロジェクトに戻ってデッドロックが発生した場合:
次のエラーログ(MySQLの)を見てください.

BACKGROUND THREAD
-----------------
srv_master_thread loops: 990 srv_active, 0 srv_shutdown, 8035 srv_idle
srv_master_thread log flush and writes: 9025
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 3350
OS WAIT ARRAY INFO: signal count 3305
RW-shared spins 0, rounds 4136, OS waits 2044
RW-excl spins 0, rounds 561, OS waits 14
RW-sx spins 56, rounds 303, OS waits 3
Spin rounds per wait: 4136.00 RW-shared, 561.00 RW-excl, 5.41 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-03-21 15:10:36 0x2d08
*** (1) TRANSACTION:
TRANSACTION 3342674, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
LOCK WAIT 6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 142, OS thread handle 2932, query id 61023 localhost 127.0.0.1 root update
insert into XXXX
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342674 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 3342675, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 147, OS thread handle 11528, query id 61024 localhost 127.0.0.1 root update
insert into XXX
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342675 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4580 page no 3 n bits 72 index PRIMARY of table `XXX`.`XXX` trx id 3342675 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 3343825
Purge done for trx's n:o < 3343825 undo n:o < 0 state: running but idle
History list length 2
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 283307779134672, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779139904, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779139032, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779137288, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779133800, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779132928, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779135544, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779132056, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 283307779131184, not started
0 lock struct(s), heap size 1136, 0 row lock(s)

プロジェクトにかかわるので、テーブルとinsertのフィールドを非表示にしただけです.他はすべてそのままログです.
上には2つのトランザクションがあり、この2つのトランザクションではdeleteとinsertの操作が行われています.delete文削除は、インデックスのないフィールドを使用して削除され、データが削除されます.Insert文の新規データはdelete文で削除されたデータです.
前述の経験によれば、delete削除時にXロックとGAPロックが加算されるので、2つのトランザクションがdeleteの場合、GAPロックは互換性があるため、すべての2つのトランザクションが同じギャップのロックを取得します.次にinsertを実行すると、同じギャップロックに挿入意向ロックが申請されます.挿入意向ロックとGAPロックは互換性がないからです.すべてのトランザクションAは、トランザクションBがギャップロックを解放するのを待っています.トランザクションBは、トランザクションAがギャップロックを解放するのを待っています.これにより、上のデッドロックが発生します.