sql >> Database >  >> RDS >> Mysql

Deadlocks in MySQL en PostgreSQL begrijpen

Door met databases te werken, is gelijktijdigheidscontrole het concept dat ervoor zorgt dat databasetransacties gelijktijdig worden uitgevoerd zonder de gegevensintegriteit te schenden.

Er is veel theorie en verschillende benaderingen rond dit concept en hoe het te bereiken, maar we zullen kort verwijzen naar de manier waarop PostgreSQL en MySQL (bij gebruik van InnoDB) ermee omgaan, en een veelvoorkomend probleem dat kan optreden in zeer gelijktijdige systemen:impasses.

Deze engines implementeren gelijktijdigheidscontrole met behulp van een methode genaamd MVCC (Multiversion Concurrency Control). Bij deze methode zullen de wijzigingen, wanneer een item wordt bijgewerkt, niet de oorspronkelijke gegevens overschrijven, maar in plaats daarvan wordt een nieuwe versie van het item (met de wijzigingen) gemaakt. We zullen dus verschillende versies van het item hebben opgeslagen.

Een van de belangrijkste voordelen van dit model is dat vergrendelingen die zijn verkregen voor het opvragen (lezen) van gegevens niet in strijd zijn met vergrendelingen die zijn verkregen voor het schrijven van gegevens, en dus blokkeert lezen nooit schrijven en schrijven blokkeert nooit lezen.

Maar als er meerdere versies van hetzelfde item zijn opgeslagen, welke versie ervan zal een transactie dan zien? Om die vraag te beantwoorden, moeten we het concept van transactie-isolatie herzien. Transacties specificeren een isolatieniveau, dat de mate bepaalt waarin een transactie moet worden geïsoleerd van resource- of gegevenswijzigingen die door andere transacties zijn aangebracht. Deze graad houdt rechtstreeks verband met de vergrendeling die door een transactie wordt gegenereerd, en kan dus, aangezien het op transactieniveau kan worden gespecificeerd, de impact bepalen die een lopende transactie kan hebben op andere lopende transacties.

Dit is een zeer interessant en lang onderwerp, hoewel we in deze blog niet te veel in detail zullen treden. We raden de officiële documentatie van PostgreSQL en MySQL aan voor meer informatie over dit onderwerp.

Dus, waarom gaan we in op de bovenstaande onderwerpen als we te maken hebben met impasses? Omdat sql-opdrachten automatisch vergrendelingen verwerven om het MVCC-gedrag te garanderen, en het verkregen vergrendelingstype afhankelijk is van de gedefinieerde transactie-isolatie.

Er zijn verschillende soorten sloten (nog een lang en interessant onderwerp om te bespreken voor PostgreSQL en MySQL), maar het belangrijkste is hoe ze met elkaar omgaan (precies hoe ze conflicteren). Waarom is dat? Omdat twee transacties niet tegelijkertijd vergrendelingen van conflicterende modi op hetzelfde object kunnen bevatten. En een niet-klein detail, eenmaal verkregen, wordt normaal gesproken vastgehouden tot het einde van de transactie.

Dit is een PostgreSQL-voorbeeld van hoe vergrendelingstypen met elkaar conflicteren:

Conflict met PostgreSQL-vergrendelingstypen

En voor MySQL:

Conflict MySQL-vergrendelingstypen

X=exclusief slot         IX=intentie exclusief slot
S=gedeeld slot         IS=intentie gedeeld slot

Dus wat gebeurt er als ik twee lopende transacties heb die tegelijkertijd conflicterende sloten op hetzelfde object willen houden? Een van hen krijgt het slot en de ander zal moeten wachten.

Dus nu zijn we in een positie om echt te begrijpen wat er gebeurt tijdens een impasse.

Wat is een impasse dan? Zoals je je kunt voorstellen, zijn er verschillende definities voor een database-impasse, maar ik vind het volgende leuk vanwege de eenvoud.

Een database-deadlock is een situatie waarin twee of meer transacties op elkaar wachten om de locks op te geven.

De volgende situatie zal ons bijvoorbeeld tot een impasse leiden:

Deadlock-voorbeeld

Hier krijgt applicatie A een slot op tabel 1 rij 1 om een ​​update uit te voeren.

Tegelijkertijd krijgt applicatie B een slot op tafel 2 rij 2.

Nu moet applicatie A een slot krijgen op tabel 2, rij 2, om de uitvoering voort te zetten en de transactie te voltooien, maar het kan het slot niet krijgen omdat het wordt vastgehouden door applicatie B. Applicatie A moet wachten tot applicatie B het vrijgeeft .

Maar applicatie B moet een vergrendeling krijgen op tabel 1 rij 1, om de uitvoering voort te zetten en de transactie te voltooien, maar het kan de vergrendeling niet krijgen omdat deze wordt vastgehouden door applicatie A.

Hier zitten we dus in een impasse. Applicatie A wacht op de resource van applicatie B om te voltooien en applicatie B wacht op de resource van applicatie A. Dus, hoe verder? De database-engine zal de deadlock detecteren en een van de transacties beëindigen, de andere deblokkeren en een deadlock-fout veroorzaken bij de gedode transactie.

Laten we enkele voorbeelden van PostgreSQL- en MySQL-deadlock bekijken:

PostgreSQL

Stel dat we een testdatabase hebben met informatie uit de landen van de wereld.

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)

We hebben twee sessies die wijzigingen willen aanbrengen in de database.

De eerste sessie wijzigt het regioveld voor de NLD-code en het populatieveld voor de AUS-code.

De tweede sessie zal het regioveld voor de AUS-code en het bevolkingsveld voor de NLD-code wijzigen.

Tabelgegevens:

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

Sessie 1:

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

Sessie 2:

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

Sessie 2 blijft hangen totdat Sessie 1 is afgelopen.

Sessie 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 hebben we onze impasse. Het systeem heeft de impasse gedetecteerd en sessie 1 afgebroken.

Sessie 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

En we kunnen controleren of de tweede sessie correct is afgelopen nadat de impasse werd gedetecteerd en de sessie 1 werd beëindigd (de vergrendeling werd dus vrijgegeven).

Voor meer details kunnen we de log in onze PostgreSQL-server zien:

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 kunnen we de daadwerkelijke commando's zien die werden gedetecteerd tijdens een deadlock.

Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaper

MySQL

Om een ​​impasse in MySQL te simuleren, kunnen we het volgende doen.

Stel dat we, net als bij PostgreSQL, een testdatabase hebben met informatie over onder meer acteurs en films.

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)

We hebben twee processen die wijzigingen in de database willen aanbrengen.

Het eerste proces wijzigt het veld first_name voor actor_id 1 en het veld last_name voor actor_id 7.

Het tweede proces wijzigt het veld first_name voor actor_id 7 en het veld last_name voor actor_id 1.

Tabelgegevens:

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

Sessie 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

Sessie 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';

Sessie 2 blijft hangen totdat Sessie 1 is afgelopen.

Sessie 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 hebben we onze impasse. Het systeem heeft de impasse gedetecteerd en sessie 1 afgebroken.

Sessie 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

Zoals we in de fout kunnen zien, zoals we zagen voor PostgreSQL, is er een impasse tussen beide processen.

Voor meer details kunnen we het commando SHOW ENGINE INNODB STATUS\G gebruiken:

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)

Onder de titel "LATEST DETECTED DEADLOCK" kunnen we details van onze impasse zien.

Om de details van de deadlock in het mysql-foutlogboek te zien, moeten we de optie innodb_print_all_deadlocks in onze database inschakelen.

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

MySQL-logfout:

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)

Rekening houdend met wat we hierboven hebben geleerd over waarom deadlocks optreden, kun je zien dat we aan de databasekant niet veel kunnen doen om ze te vermijden. Hoe dan ook, als DBA's is het onze plicht om ze daadwerkelijk te vangen, te analyseren en feedback te geven aan de ontwikkelaars.

De realiteit is dat deze fouten specifiek zijn voor elke toepassing, dus u moet ze een voor een controleren en er is geen handleiding om u te vertellen hoe u dit kunt oplossen. Als u dit in gedachten houdt, zijn er enkele dingen waar u naar kunt zoeken.

Tips voor het onderzoeken en vermijden van impasses

Zoeken naar langlopende transacties. Aangezien de vergrendelingen meestal worden vastgehouden tot het einde van een transactie, geldt hoe langer de transactie, hoe langer de vergrendelingen over de middelen. Probeer indien mogelijk langlopende transacties op te splitsen in kleinere/snellere transacties.

Soms is het niet mogelijk om de transacties daadwerkelijk te splitsen, dus het werk moet erop gericht zijn om die bewerkingen elke keer in een consistente volgorde uit te voeren, zodat transacties goed gedefinieerde wachtrijen vormen en niet vastlopen.

Een tijdelijke oplossing die u ook kunt voorstellen, is om logica voor opnieuw proberen aan de app toe te voegen (probeer natuurlijk eerst het onderliggende probleem op te lossen) zodat, als er een impasse optreedt, de app dezelfde opdrachten opnieuw uitvoert.

Controleer de gebruikte isolatieniveaus, soms probeert u deze te wijzigen. Zoek naar commando's zoals SELECT FOR UPDATE en SELECT FOR SHARE, aangezien ze expliciete vergrendelingen genereren, en evalueer of ze echt nodig zijn of je kunt werken met een oudere momentopname van de gegevens. Een ding dat u kunt proberen als u deze opdrachten niet kunt verwijderen, is door een lager isolatieniveau te gebruiken, zoals READ COMMITTED.

Voeg natuurlijk altijd goed gekozen indexen toe aan uw tabellen. Dan hoeven uw zoekopdrachten minder indexrecords te scannen en dus minder vergrendelingen in te stellen.

Op een hoger niveau kunt u als DBA enkele voorzorgsmaatregelen nemen om vergrendeling in het algemeen te minimaliseren. Om een ​​voorbeeld te noemen, in dit geval voor PostgreSQL, kunt u voorkomen dat u een standaardwaarde toevoegt aan dezelfde opdracht als waarmee u een kolom toevoegt. Als u een tabel wijzigt, krijgt u een zeer agressieve vergrendeling en als u er een standaardwaarde voor instelt, worden de bestaande rijen met null-waarden daadwerkelijk bijgewerkt, waardoor deze bewerking erg lang duurt. Dus als u deze bewerking opsplitst in verschillende opdrachten, de kolom toevoegt, de standaard toevoegt, de null-waarden bijwerkt, minimaliseert u de vergrendelingsimpact.

Natuurlijk zijn er talloze tips zoals deze die de DBA's krijgen met de oefening (gelijktijdig indexen maken, de pk-index afzonderlijk maken voordat de pk wordt toegevoegd, enzovoort), maar het belangrijkste is om deze "manier van denken" en altijd om de vergrendelingsimpact van de operaties die we doen te minimaliseren.

Samenvatting

Hopelijk heeft deze blog u nuttige informatie gegeven over database-impasses en hoe u deze kunt overwinnen. Aangezien er geen onfeilbare manier is om impasses te vermijden, kan het helpen om ze te ondervangen voordat ze schade toebrengen aan uw database-instanties. Softwareoplossingen zoals ClusterControl kunnen u helpen ervoor te zorgen dat uw databases altijd in vorm blijven. ClusterControl heeft al honderden ondernemingen geholpen - wordt die van u de volgende? Download vandaag nog uw gratis proefversie van ClusterControl om te zien of deze geschikt is voor uw databasebehoeften.


  1. Is het in Oracle mogelijk om een ​​record in te voegen of te UPDATEN via een weergave?

  2. Een overzicht van SQL Join-types met voorbeelden

  3. 4 manieren om uren, minuten en seconden te scheiden van een tijdwaarde in MariaDB

  4. Configureer SQL Server Always ON-beschikbaarheidsgroepen tussen twee synchrone replica's. Deel 2