Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Verstehen von Deadlocks in MySQL und PostgreSQL

Bei der Arbeit mit Datenbanken ist Parallelitätssteuerung das Konzept, das sicherstellt, dass Datenbanktransaktionen gleichzeitig ausgeführt werden, ohne die Datenintegrität zu verletzen.

Es gibt eine Menge Theorie und verschiedene Ansätze zu diesem Konzept und wie man es bewerkstelligt, aber wir werden kurz auf die Art und Weise eingehen, wie PostgreSQL und MySQL (bei Verwendung von InnoDB) damit umgehen, und auf ein häufiges Problem, das in hochgradig gleichzeitigen Systemen auftreten kann:Deadlocks.

Diese Engines implementieren die Parallelitätssteuerung mithilfe einer Methode namens MVCC (Multiversion Concurrency Control). Bei dieser Methode werden beim Aktualisieren eines Elements die ursprünglichen Daten nicht durch die Änderungen überschrieben, sondern es wird eine neue Version des Elements (mit den Änderungen) erstellt. Daher haben wir mehrere Versionen des Artikels gespeichert.

Einer der Hauptvorteile dieses Modells besteht darin, dass Sperren, die zum Abfragen (Lesen) von Daten erworben wurden, nicht mit Sperren kollidieren, die zum Schreiben von Daten erworben wurden, sodass das Lesen niemals das Schreiben blockiert und das Schreiben niemals das Lesen blockiert.

Aber wenn mehrere Versionen desselben Artikels gespeichert sind, welche Version davon wird bei einer Transaktion angezeigt? Um diese Frage zu beantworten, müssen wir das Konzept der Transaktionsisolierung überprüfen. Transaktionen geben eine Isolationsstufe an, die den Grad definiert, in dem eine Transaktion von Ressourcen- oder Datenänderungen isoliert werden muss, die von anderen Transaktionen vorgenommen werden. Dieser Grad steht in direktem Zusammenhang mit der von einer Transaktion erzeugten Sperrung und kann daher, da er auf Transaktionsebene angegeben werden kann, die Auswirkungen bestimmen, die eine laufende Transaktion gegenüber anderen laufenden Transaktionen haben kann.

Dies ist ein sehr interessantes und langes Thema, obwohl wir in diesem Blog nicht auf zu viele Details eingehen werden. Wir empfehlen die offizielle PostgreSQL- und MySQL-Dokumentation zur weiteren Lektüre zu diesem Thema.

Warum gehen wir also auf die oben genannten Themen ein, wenn es um Deadlocks geht? Weil SQL-Befehle automatisch Sperren erwerben, um das MVCC-Verhalten sicherzustellen, und der erworbene Sperrtyp von der definierten Transaktionsisolation abhängt.

Es gibt mehrere Arten von Sperren (wiederum ein weiteres langes und interessantes Thema für PostgreSQL und MySQL), aber das Wichtigste an ihnen ist, wie sie miteinander interagieren (am genauesten, wie sie in Konflikt geraten). Warum ist das so? Weil zwei Transaktionen nicht gleichzeitig Sperren widersprüchlicher Modi für dasselbe Objekt halten können. Und ein nicht unbedeutendes Detail, sobald es erfasst wurde, wird normalerweise bis zum Ende der Transaktion gesperrt.

Dies ist ein PostgreSQL-Beispiel dafür, wie Sperrtypen miteinander in Konflikt geraten:

Konflikt bei PostgreSQL-Sperrtypen

Und für MySQL:

Konflikt bei MySQL-Sperrtypen

X=exklusive Sperre         IX=beabsichtigte exklusive Sperre
S=geteilte Sperre         IS=beabsichtigte geteilte Sperre

Was passiert also, wenn ich zwei laufende Transaktionen habe, die gleichzeitig widersprüchliche Sperren für dasselbe Objekt halten möchten? Einer von ihnen bekommt das Schloss und der andere muss warten.

Jetzt sind wir also in der Lage, wirklich zu verstehen, was während eines Deadlocks passiert.

Was ist dann ein Deadlock? Wie Sie sich vorstellen können, gibt es mehrere Definitionen für einen Datenbank-Deadlock, aber ich mag die folgende wegen ihrer Einfachheit.

Ein Datenbank-Deadlock ist eine Situation, in der zwei oder mehr Transaktionen darauf warten, dass die andere Sperre aufgibt.

So führt beispielsweise die folgende Situation zu einem Deadlock:

Deadlock-Beispiel

Hier erhält Anwendung A eine Sperre für Tabelle 1, Zeile 1, um eine Aktualisierung vorzunehmen.

Gleichzeitig erhält Anwendung B eine Sperre auf Tabelle 2 Zeile 2.

Jetzt muss Anwendung A eine Sperre für Tabelle 2, Zeile 2 erhalten, um die Ausführung fortzusetzen und die Transaktion abzuschließen, aber sie kann die Sperre nicht erhalten, da sie von Anwendung B gehalten wird. Anwendung A muss warten, bis Anwendung B sie freigibt .

Aber Anwendung B muss eine Sperre für Tabelle 1, Zeile 1 erhalten, um die Ausführung fortzusetzen und die Transaktion abzuschließen, aber sie kann die Sperre nicht erhalten, da sie von Anwendung A gehalten wird.

Hier befinden wir uns also in einer Sackgasse. Anwendung A wartet auf die von Anwendung B gehaltene Ressource, um fertig zu werden, und Anwendung B wartet auf die von Anwendung A gehaltene Ressource. Wie also fortfahren? Die Datenbank-Engine erkennt den Deadlock und beendet eine der Transaktionen, entsperrt die andere und löst einen Deadlock-Fehler bei der beendeten Transaktion aus.

Sehen wir uns einige PostgreSQL- und MySQL-Deadlock-Beispiele an:

PostgreSQL

Angenommen, wir haben eine Testdatenbank mit Informationen aus den Ländern der Welt.

world=# SELECT code,region,population FROM country WHERE code IN ('NLD','AUS');
code |          region           | population
------+---------------------------+------------
NLD  | Western Europe            |   15864000
AUS  | Australia and New Zealand |   18886000
(2 rows)

Wir haben zwei Sitzungen, die Änderungen an der Datenbank vornehmen möchten.

Die erste Sitzung ändert das Regionsfeld für den NLD-Code und das Bevölkerungsfeld für den AUS-Code.

Die zweite Sitzung ändert das Regionsfeld für den AUS-Code und das Bevölkerungsfeld für den NLD-Code.

Tabellendaten:

code: NLD
region: Western Europe
population: 15864000
code: AUS
region: Australia and New Zealand
population: 18886000

Sitzung 1:

world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Europe' WHERE code='NLD';
UPDATE 1

Sitzung 2:

world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';

Sitzung 2 bleibt hängen und wartet darauf, dass Sitzung 1 beendet wird.

Sitzung 1:

world=# UPDATE country SET population=18886001 WHERE code='AUS';

ERROR:  deadlock detected
DETAIL:  Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
HINT:  See server log for query details.
CONTEXT:  while updating tuple (0,15) in relation "country"

Hier haben wir unsere Sackgasse. Das System hat den Deadlock erkannt und Sitzung 1 abgebrochen.

Sitzung 2:

world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';
UPDATE 1

Und wir können überprüfen, ob die zweite Sitzung korrekt beendet wurde, nachdem der Deadlock erkannt und die Sitzung 1 beendet wurde (also die Sperre aufgehoben wurde).

Um weitere Details zu erhalten, können wir das Protokoll auf unserem PostgreSQL-Server einsehen:

2018-05-16 12:56:38.520 -03 [1181] ERROR:  deadlock detected
2018-05-16 12:56:38.520 -03 [1181] DETAIL:  Process 1181 waits for ShareLock on transaction 579; blocked by process 1148.
       Process 1148 waits for ShareLock on transaction 578; blocked by process 1181.
       Process 1181: UPDATE country SET population=18886001 WHERE code='AUS';
       Process 1148: UPDATE country SET population=15864001 WHERE code='NLD';
2018-05-16 12:56:38.520 -03 [1181] HINT:  See server log for query details.
2018-05-16 12:56:38.520 -03 [1181] CONTEXT:  while updating tuple (0,15) in relation "country"
2018-05-16 12:56:38.520 -03 [1181] STATEMENT:  UPDATE country SET population=18886001 WHERE code='AUS';
2018-05-16 12:59:50.568 -03 [1181] ERROR:  current transaction is aborted, commands ignored until end of transaction block

Hier können wir die tatsächlichen Befehle sehen, die bei einem Deadlock erkannt wurden.

Laden Sie noch heute das Whitepaper PostgreSQL-Verwaltung und -Automatisierung mit ClusterControl herunterErfahren Sie, was Sie wissen müssen, um PostgreSQL bereitzustellen, zu überwachen, zu verwalten und zu skalierenLaden Sie das Whitepaper herunter

MySQL

Um einen Deadlock in MySQL zu simulieren, können wir Folgendes tun.

Angenommen, wir haben wie bei PostgreSQL eine Testdatenbank mit Informationen unter anderem zu Schauspielern und Filmen.

mysql> SELECT first_name,last_name FROM actor WHERE actor_id IN (1,7);
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| PENELOPE   | GUINESS   |
| GRACE      | MOSTEL    |
+------------+-----------+
2 rows in set (0.00 sec)

Wir haben zwei Prozesse, die Änderungen an der Datenbank vornehmen möchten.

Der erste Prozess ändert das Feld first_name für actor_id 1 und das Feld last_name für actor_id 7.

Der zweite Prozess ändert das Feld first_name für actor_id 7 und das Feld last_name für actor_id 1.

Tabellendaten:

actor_id: 1
first_name: PENELOPE
last_name: GUINESS
actor_id: 7
first_name: GRACE
last_name: MOSTEL

Sitzung 1:

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='GUINESS' WHERE actor_id='1';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Sitzung 2:

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';

Sitzung 2 bleibt hängen und wartet darauf, dass Sitzung 1 beendet wird.

Sitzung 1:

mysql> UPDATE actor SET last_name='GRACE' WHERE actor_id='7';

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Hier haben wir unsere Sackgasse. Das System hat den Deadlock erkannt und Sitzung 1 abgebrochen.

Sitzung 2:

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE actor SET first_name='MOSTEL' WHERE actor_id='7';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1';
Query OK, 1 row affected (8.52 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Wie wir im Fehler sehen können, gibt es, wie wir es bei PostgreSQL gesehen haben, einen Deadlock zwischen beiden Prozessen.

Für weitere Details können wir den Befehl SHOW ENGINE INNODB STATUS\G:

verwenden
mysql> SHOW ENGINE INNODB STATUS\G
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-05-16 18:55:46 0x7f4c34128700
*** (1) TRANSACTION:
TRANSACTION 1456, ACTIVE 33 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 54, OS thread handle 139965388506880, query id 15876 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1456 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc   ;;
1: len 6; hex 0000000005af; asc       ;;
2: len 7; hex 2d000001690110; asc -   i  ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z   ;;

*** (2) TRANSACTION:
TRANSACTION 1455, ACTIVE 47 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 53, OS thread handle 139965267871488, query id 16013 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc   ;;
1: len 6; hex 0000000005af; asc       ;;
2: len 7; hex 2d000001690110; asc -   i  ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afca8b3; asc Z   ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1455 lock_mode X locks rec but not gap waiting
Record lock, heap no 202 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc   ;;
1: len 6; hex 0000000005b0; asc       ;;
2: len 7; hex 2e0000016a0110; asc .   j  ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afca8c1; asc Z   ;;

*** WE ROLL BACK TRANSACTION (2)

Unter dem Titel "LETZT ENTDECKTER DEADLOCK" sehen wir Details unseres Deadlocks.

Um die Details des Deadlocks im MySQL-Fehlerprotokoll zu sehen, müssen wir die Option innodb_print_all_deadlocks in unserer Datenbank aktivieren.

mysql> set global innodb_print_all_deadlocks=1;
Query OK, 0 rows affected (0.00 sec)

MySQL-Protokollfehler:

2018-05-17T18:36:58.341835Z 12 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2018-05-17T18:36:58.341869Z 12 [Note] InnoDB:
*** (1) TRANSACTION:
 
TRANSACTION 1812, ACTIVE 42 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 11, OS thread handle 140515492943616, query id 8467 localhost root updating
UPDATE actor SET last_name='PENELOPE' WHERE actor_id='1'
2018-05-17T18:36:58.341945Z 12 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
 
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1812 lock_mode X locks rec but not gap waiting
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc   ;;
1: len 6; hex 000000000713; asc       ;;
2: len 7; hex 330000016b0110; asc 3   k  ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z   ;;
 
2018-05-17T18:36:58.342347Z 12 [Note] InnoDB: *** (2) TRANSACTION:
 
TRANSACTION 1811, ACTIVE 65 sec starting index read, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 12, OS thread handle 140515492677376, query id 9075 localhost root updating
UPDATE actor SET last_name='GRACE' WHERE actor_id='7'
2018-05-17T18:36:58.342409Z 12 [Note] InnoDB: *** (2) HOLDS THE LOCK(S):
 
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap
Record lock, heap no 204 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0001; asc   ;;
1: len 6; hex 000000000713; asc       ;;
2: len 7; hex 330000016b0110; asc 3   k  ;;
3: len 7; hex 4755494e455353; asc GUINESS;;
4: len 7; hex 4755494e455353; asc GUINESS;;
5: len 4; hex 5afdcb89; asc Z   ;;
 
2018-05-17T18:36:58.342793Z 12 [Note] InnoDB: *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
 
RECORD LOCKS space id 23 page no 3 n bits 272 index PRIMARY of table `sakila`.`actor` trx id 1811 lock_mode X locks rec but not gap waiting
Record lock, heap no 205 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 2; hex 0007; asc   ;;
1: len 6; hex 000000000714; asc       ;;
2: len 7; hex 340000016c0110; asc 4   l  ;;
3: len 6; hex 4d4f5354454c; asc MOSTEL;;
4: len 6; hex 4d4f5354454c; asc MOSTEL;;
5: len 4; hex 5afdcba0; asc Z   ;;
 
2018-05-17T18:36:58.343105Z 12 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)

Unter Berücksichtigung dessen, was wir oben darüber gelernt haben, warum Deadlocks auftreten, können Sie sehen, dass wir auf der Datenbankseite nicht viel tun können, um sie zu vermeiden. Wie auch immer, als DBAs ist es unsere Pflicht, sie tatsächlich zu erfassen, zu analysieren und den Entwicklern Feedback zu geben.

Die Realität ist, dass diese Fehler für jede Anwendung spezifisch sind, also müssen Sie sie einzeln überprüfen, und es gibt keine Anleitung, die Ihnen sagt, wie Sie dies beheben können. Wenn Sie dies im Hinterkopf behalten, gibt es einige Dinge, nach denen Sie suchen können.

Tipps zum Untersuchen und Vermeiden von Deadlocks

Suchen Sie nach Transaktionen mit langer Laufzeit. Da die Sperren normalerweise bis zum Ende einer Transaktion gehalten werden, gilt:Je länger die Transaktion, desto länger die Sperren über den Ressourcen. Wenn es möglich ist, versuchen Sie, Transaktionen mit langer Laufzeit in kleinere/schnellere aufzuteilen.

Manchmal ist es nicht möglich, die Transaktionen tatsächlich aufzuteilen, daher sollte sich die Arbeit darauf konzentrieren, diese Operationen jedes Mal in einer konsistenten Reihenfolge auszuführen, damit Transaktionen gut definierte Warteschlangen bilden und keine Deadlocks auftreten.

Eine Problemumgehung, die Sie auch vorschlagen können, besteht darin, der Anwendung Wiederholungslogik hinzuzufügen (versuchen Sie natürlich zuerst, das zugrunde liegende Problem zu lösen), sodass die Anwendung im Falle eines Deadlocks dieselben Befehle erneut ausführt.

Überprüfen Sie die verwendeten Isolationsstufen, manchmal versuchen Sie es, indem Sie sie ändern. Suchen Sie nach Befehlen wie SELECT FOR UPDATE und SELECT FOR SHARE, da diese explizite Sperren generieren, und prüfen Sie, ob sie wirklich benötigt werden oder ob Sie mit einem älteren Snapshot der Daten arbeiten können. Eine Sache, die Sie versuchen können, wenn Sie diese Befehle nicht entfernen können, ist die Verwendung einer niedrigeren Isolationsstufe wie READ COMMITTED.

Fügen Sie Ihren Tabellen natürlich immer gut ausgewählte Indizes hinzu. Dann müssen Ihre Abfragen weniger Indexdatensätze scannen und folglich weniger Sperren setzen.

Auf einer höheren Ebene können Sie als DBA einige Vorkehrungen treffen, um das Sperren im Allgemeinen zu minimieren. Um ein Beispiel zu nennen, in diesem Fall für PostgreSQL, können Sie vermeiden, einen Standardwert in demselben Befehl hinzuzufügen, in dem Sie eine Spalte hinzufügen. Das Ändern einer Tabelle wird eine wirklich aggressive Sperre erhalten, und das Festlegen eines Standardwerts dafür aktualisiert tatsächlich die vorhandenen Zeilen, die Nullwerte haben, wodurch dieser Vorgang sehr lange dauert. Wenn Sie diese Operation also in mehrere Befehle aufteilen, die Spalte hinzufügen, den Standardwert hinzufügen und die Nullwerte aktualisieren, minimieren Sie die Sperrwirkung.

Natürlich gibt es unzählige Tipps wie diese, die die DBAs durch die Praxis erhalten (gleichzeitiges Erstellen von Indizes, separates Erstellen des pk-Index vor dem Hinzufügen des pk usw.), aber das Wichtigste ist, diese "Methode von denken" und stets die Auswirkungen der von uns durchgeführten Operationen auf die Sperre zu minimieren.

Zusammenfassung

Hoffentlich hat Ihnen dieser Blog hilfreiche Informationen zu Datenbank-Deadlocks und deren Überwindung geliefert. Da es keine sichere Möglichkeit gibt, Deadlocks zu vermeiden, kann Ihnen das Wissen um ihre Funktionsweise helfen, sie abzufangen, bevor sie Ihren Datenbankinstanzen Schaden zufügen. Softwarelösungen wie ClusterControl können Ihnen helfen sicherzustellen, dass Ihre Datenbanken immer in Form bleiben. ClusterControl hat bereits Hunderten von Unternehmen geholfen – wird Ihres das nächste sein? Laden Sie noch heute Ihre kostenlose Testversion von ClusterControl herunter, um zu sehen, ob es für Ihre Datenbankanforderungen geeignet ist.