Beim Testen des gleichzeitigen Sendens von Gutscheinen an Benutzer in der Testumgebung ist ein Deadlock aufgetreten. Ich habe dieses Problem durch die Suche nach relevanten Informationen gelöst, daher habe ich darüber nachgedacht, es zusammenzufassen. Daher handelt der folgende Artikel hauptsächlich von einem MySQL Vollständige Aufzeichnung des Deadlock-Fehlerbehebungsprozesses. Freunde, die es benötigen, können darauf zurückgreifen.
Vorwort
Die Datenbank-Deadlocks, auf die ich zuvor gestoßen bin, waren alle Deadlocks, die durch eine inkonsistente Sperrreihenfolge während Batch-Updates verursacht wurden, aber Zhou oben ist auf einen Deadlock gestoßen das war schwer zu verstehen. Ich nutzte diese Gelegenheit, um das Wissen über MySQL-Deadlocks und häufige Deadlock-Szenarien erneut zu erlernen. Nach mehreren Untersuchungen und Gesprächen mit Kollegen habe ich endlich die Ursache für dieses Deadlock-Problem entdeckt und viel gewonnen. Obwohl wir Back-End-Programmierer sind, müssen wir den Quellcode im Zusammenhang mit Sperren nicht so gründlich analysieren wie DBAs. Wenn wir jedoch grundlegende Methoden zur Fehlerbehebung bei Deadlocks beherrschen, wird dies für unsere tägliche Entwicklung von großem Nutzen sein.
PS: In diesem Artikel werden nicht die Grundkenntnisse von Deadlock vorgestellt. Informationen zum Sperrprinzip von MySQL finden Sie im Referenzmaterial dieses Artikels.
Ursache des Deadlocks
Stellen Sie zunächst die Datenbank- und Tabellensituation vor. Da es sich um reale Daten innerhalb des Unternehmens handelt, wurde Folgendes simuliert und hat keinen Einfluss auf die spezifische Analyse .
Wir verwenden die 5.5-Version der MySQL-Datenbank. Die Transaktionsisolationsstufe ist die Standard-RR (Repeatable-Read) und verwendet die Innodb-Engine. Angenommen, es gibt eine Testtabelle:
CREATE TABLE `test` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `a` int(11) unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `a` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;
Die Struktur der Tabelle ist sehr einfach, mit einer Primärschlüssel-ID und einem weiteren eindeutigen Index a. Die Daten in der Tabelle lauten wie folgt:
mysql> select * from test; +----+------+ | id | a | +----+------+ | 1 | 1 | | 2 | 2 | | 4 | 4 | +----+------+ 3 rows in set (0.00 sec)
Der Vorgang, der einen Deadlock verursacht, ist wie folgt:
步骤 | 事务1 | 事务2 |
---|---|---|
1 | begin | |
2 | delete from test where a = 2; | |
3 | begin | |
4 | delete from test where a = 2; (事务1卡住) | |
5 | 提示出现死锁:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | insert into test (id, a) values (10, 2); |
然后我们可以通过SHOW ENGINE INNODB STATUS;
来查看死锁日志:
------------------------ LATEST DETECTED DEADLOCK ------------------------ 170219 13:31:31 *** (1) TRANSACTION: TRANSACTION 2A8BD, ACTIVE 11 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 448218, OS thread handle 0x2abe5fb5d700, query id 18923238 renjun.fangcloud.net 121.41.41.92 root updating delete from test where a = 2 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BD lock_mode X waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;; *** (2) TRANSACTION: TRANSACTION 2A8BC, ACTIVE 18 sec inserting mysql tables in use 1, locked 1 4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2 MySQL thread id 448217, OS thread handle 0x2abe5fd65700, query id 18923239 renjun.fangcloud.net 121.41.41.92 root update insert into test (id,a) values (10,2) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock mode S waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;; *** WE ROLL BACK TRANSACTION (1)
分析
阅读死锁日志
遇到死锁,第一步就是阅读死锁日志。死锁日志通常分为两部分,上半部分说明了事务1在等待什么锁:
170219 13:31:31 *** (1) TRANSACTION: TRANSACTION 2A8BD, ACTIVE 11 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 448218, OS thread handle 0x2abe5fb5d700, query id 18923238 renjun.fangcloud.net 121.41.41.92 root updating delete from test where a = 2 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BD lock_mode X waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;;
从日志里我们可以看到事务1当前正在执行delete from test where a = 2
,该条语句正在申请索引a的X锁,所以提示lock_mode X waiting
。
然后日志的下半部分说明了事务2当前持有的锁以及等待的锁:
*** (2) TRANSACTION: TRANSACTION 2A8BC, ACTIVE 18 sec inserting mysql tables in use 1, locked 1 4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 2 MySQL thread id 448217, OS thread handle 0x2abe5fd65700, query id 18923239 renjun.fangcloud.net 121.41.41.92 root update insert into test (id,a) values (10,2) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 923 n bits 80 index `a` of table `oauthdemo`.`test` trx id 2A8BC lock mode S waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 00000002; asc ;; 1: len 4; hex 00000002; asc ;;
从日志的HOLDS THE LOCKS(S)
块中我们可以看到事务2持有索引a的X锁,并且是记录锁(Record Lock)。该锁是通过事务2在步骤2执行的delete语句申请的。由于是RR隔离模式下的基于唯一索引的等值查询(Where a = 2),所以会申请一个记录锁,而非next-key锁。
从日志的WAITING FOR THIS LOCK TO BE GRANTED
块中我们可以看到事务2正在申请S锁,也就是共享锁。该锁是insert into test (id,a) values (10,2)语句申请的。insert语句在普通情况下是会申请排他锁,也就是X锁,但是这里出现了S锁。这是因为a字段是一个唯一索引,所以insert语句会在插入前进行一次duplicate key
的检查,为了使这次检查成功,需要申请S锁防止其他事务对a字段进行修改。
那么为什么该S锁会失败呢?这是对同一个字段的锁的申请是需要排队的。S锁前面还有一个未申请成功的X锁,所以S锁必须等待,所以形成了循环等待,死锁出现了。
通过阅读死锁日志,我们可以清楚地知道两个事务形成了怎样的循环等待,再加以分析,就可以逆向推断出循环等待的成因,也就是死锁形成的原因。
死锁形成流程图
为了让大家更好地理解死锁形成的原因,我们再通过表格的形式阐述死锁形成的流程:
步骤 | 事务1 | 事务2 |
---|---|---|
1 | begin | |
2 | delete from test where a = 2; 执行成功,事务2占有a=2下的X锁,类型为记录锁。 | |
3 | begin | |
4 | delete from test where a = 2; 事务1希望申请a=2下的X锁,但是由于事务2已经申请了一把X锁,两把X锁互斥,所以X锁申请进入锁请求队列。 | |
5 | 出现死锁,事务1权重较小,所以被选择回滚(成为牺牲品)。 | insert into test (id, a) values (10, 2); 由于a字段建立了唯一索引,所以需要申请S锁以便检查duplicate key,由于插入的a的值还是2,所以排在X锁后面。但是前面的X锁的申请只有在事务2commit或者rollback之后才能成功,此时形成了循环等待,死锁产生。 |
Erweiterung
Während der Fehlerbehebung bei Deadlocks stellte ein Kollege außerdem fest, dass das obige Szenario zu einer anderen Art von Deadlock führen würde, der nicht manuell reproduziert werden kann Die Reproduktion ist nur in Szenarien mit hoher Parallelität möglich.
Das dem Deadlock entsprechende Protokoll wird hier nicht veröffentlicht. Der Hauptunterschied zum vorherigen Deadlock besteht darin, dass die Sperre, auf die Transaktion 2 wartet, von S-Sperre in X-Sperre geändert wird, was lock_mode X locks gap <a href="//m.sbmmt.com/java/java-Before.html" target="_blank">before</a> rec insert intention waiting
ist.
Wir verwenden weiterhin eine Tabelle, um den Prozess der Deadlock-Generierung im Detail zu erklären:
Schritte | Transaktion 1 | Transaktion 2 | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
|
beginnen | |||||||||||||||||||||
2 | Aus Test löschen, wenn a = 2; die Ausführung ist erfolgreich, Transaktion 2 belegt a=2 X-Sperre, Typ ist Datensatzsperre. | ||||||||||||||||||||||
3 | beginnen | ||||||||||||||||||||||
4 td> | [Phase 1 einfügen] In Test (id, a) Werte (10, 2) einfügen; Transaktion 2 gilt für die S-Sperre zur Überprüfung doppelter Schlüssel. Prüfung erfolgreich. | ||||||||||||||||||||||
5 | aus Test löschen, wobei a = 2; Transaktion 1 hofft, die X-Sperre unter a=2 zu beantragen, aber da Transaktion 2 dies bereits getan hat Wenn eine Sperre X angewendet wird, schließen sich die beiden X-Sperren gegenseitig aus, sodass die X-Sperranwendung in die Sperranforderungswarteschlange gelangt. | ||||||||||||||||||||||
6 | Ein Deadlock tritt auf und Transaktion 1 hat ein geringeres Gewicht, daher wird sie ausgewählt zurückgerollt (wird zum Opfer). | [Phase 2 einfügen]In Test (ID, A) einfügen Werte (10, 2); Transaktion 2 startetDaten einfügen, die S-Sperre wird auf eine X-Sperre aktualisiert und der Typ ist Einfügeabsicht. Auf die gleiche Weise tritt die X-Sperre in die Warteschlange ein, bildet eine Warteschleife und es kommt zu einem Deadlock. |
Das obige ist der detaillierte Inhalt vonTeilen der vollständigen Aufzeichnung eines Deadlock-Fehlerbehebungsprozesses in MySQL. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!