sql >> Database teknologi >  >> RDS >> Mysql

Forstå deadlocks i MySQL og PostgreSQL

Når du arbejder med databaser, er samtidighedskontrol konceptet, der sikrer, at databasetransaktioner udføres samtidigt uden at krænke dataintegriteten.

Der er en masse teori og forskellige tilgange omkring dette koncept, og hvordan man opnår det, men vi vil kort referere til den måde, PostgreSQL og MySQL (når du bruger InnoDB) håndterer det, og et almindeligt problem, der kan opstå i meget samtidige systemer:dødvande.

Disse motorer implementerer samtidighedskontrol ved at bruge en metode kaldet MVCC (Multiversion Concurrency Control). I denne metode, når et element opdateres, vil ændringerne ikke overskrive de originale data, men i stedet vil der blive oprettet en ny version af elementet (med ændringerne). Vi vil således have flere versioner af varen gemt.

En af de vigtigste fordele ved denne model er, at låse erhvervet til forespørgsel (læsning) af data ikke er i konflikt med låse erhvervet til at skrive data, og så læsning blokerer aldrig skrivning, og skrivning blokerer aldrig læsning.

Men hvis flere versioner af den samme vare er gemt, hvilken version af den vil en transaktion se? For at besvare det spørgsmål er vi nødt til at gennemgå begrebet transaktionsisolering. Transaktioner angiver et isolationsniveau, der definerer i hvilken grad en transaktion skal isoleres fra ressource- eller dataændringer foretaget af andre transaktioner. Denne grad er direkte relateret til den låsning, der genereres af en transaktion, og så, da den kan specificeres på transaktionsniveau, kan den bestemme, hvilken effekt en løbende transaktion kan have over andre løbende transaktioner.

Dette er et meget interessant og langt emne, selvom vi ikke vil gå for mange detaljer i denne blog. Vi vil anbefale den officielle PostgreSQL- og MySQL-dokumentation for yderligere læsning om dette emne.

Så hvorfor går vi ind på ovenstående emner, når vi beskæftiger os med dødvande? Fordi sql-kommandoer automatisk henter låse for at sikre MVCC-adfærden, og den opnåede låsetype afhænger af den definerede transaktionsisolation.

Der er flere typer låse (igen endnu et langt og interessant emne at gennemgå for PostgreSQL og MySQL), men det vigtige ved dem er, hvordan de interagerer (mest præcist, hvordan de er i konflikt) med hinanden. Hvorfor det? Fordi to transaktioner ikke kan holde låse af modstridende tilstande på det samme objekt på samme tid. Og en ikke-mindre detalje, når først den er erhvervet, holdes en lås normalt indtil slutningen af ​​transaktionen.

Dette er et PostgreSQL-eksempel på, hvordan låsetyper er i konflikt med hinanden:

PostgreSQL Låsetyper konflikt

Og for MySQL:

MySQL-låsetyper konflikt

X=eksklusiv lås         IX=intention exclusive lock
S=delt lås         IS=intention delt lås

Så hvad sker der, når jeg har to kørende transaktioner, der ønsker at holde modstridende låse på det samme objekt på samme tid? En af dem får låsen, og den anden må vente.

Så nu er vi i stand til virkelig at forstå, hvad der sker under et dødvande.

Hvad er et dødvande så? Som du kan forestille dig, er der adskillige definitioner for en database-deadlock, men jeg kan godt lide følgende for dens enkelhed.

En databasedeadlock er en situation, hvor to eller flere transaktioner venter på, at hinanden opgiver låse.

Så for eksempel vil følgende situation føre os til et dødvande:

eksempel på dødvande

Her får applikation A en lås på tabel 1 række 1 for at kunne lave en opdatering.

Samtidig får applikation B en lås på tabel 2 række 2.

Nu skal applikation A have en lås på tabel 2 række 2, for at fortsætte udførelsen og afslutte transaktionen, men den kan ikke få låsen, fordi den holdes af applikation B. Applikation A skal vente på, at applikation B frigiver den .

Men applikation B skal have en lås på tabel 1, række 1, for at fortsætte udførelsen og afslutte transaktionen, men den kan ikke få låsen, fordi den holdes af applikation A.

Så her er vi i en dødvande situation. Ansøgning A venter på, at den ressource, som applikation B besidder, afsluttes, og applikation B venter på den ressource, som applikation A besidder. Så hvordan fortsætter man? Databasemotoren vil opdage dødlåsen og dræbe en af ​​transaktionerne, ophæve blokeringen af ​​den anden og skabe en deadlock-fejl på den dræbte.

Lad os se nogle eksempler på PostgreSQL og MySQL dødvande:

PostgreSQL

Antag, at vi har en testdatabase med information fra verdens lande.

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)

Vi har to sessioner, der ønsker at lave ændringer i databasen.

Den første session vil ændre regionsfeltet for NLD-koden og befolkningsfeltet for AUS-koden.

Den anden session vil ændre regionsfeltet for AUS-koden og befolkningsfeltet for NLD-koden.

Tabeldata:

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

Session 1:

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

Session 2:

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

Session 2 vil hænge og vente på, at Session 1 er færdig.

Session 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"

Her har vi vores dødvande. Systemet registrerede dødvandet og afbrød session 1.

Session 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

Og vi kan kontrollere, at den anden session sluttede korrekt, efter at dødvandet blev opdaget, og session 1 blev dræbt (således blev låsen udløst).

For at få flere detaljer kan vi se loggen i vores PostgreSQL-server:

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

Her vil vi være i stand til at se de faktiske kommandoer, der blev opdaget ved dødvande.

Download Whitepaper Today PostgreSQL Management &Automation med ClusterControlFå flere oplysninger om, hvad du skal vide for at implementere, overvåge, administrere og skalere PostgreSQLDownload Whitepaper

MySQL

For at simulere en dødvande i MySQL kan vi gøre følgende.

Som med PostgreSQL, antag, at vi har en testdatabase med information om blandt andet skuespillere og film.

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)

Vi har to processer, der ønsker at foretage ændringer i databasen.

Den første proces vil ændre feltet fornavn for aktør_id 1 og feltet efternavn for aktør_id 7.

Den anden proces vil ændre feltet fornavn for aktør_id 7 og feltet efternavn for aktør_id 1.

Tabeldata:

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

Session 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

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

Session 2 vil hænge og vente på, at Session 1 er færdig.

Session 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

Her har vi vores dødvande. Systemet registrerede dødvandet og afbrød session 1.

Session 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

Som vi kan se i fejlen, som vi så for PostgreSQL, er der et dødvande mellem begge processer.

For flere detaljer kan vi bruge kommandoen SHOW ENGINE INNODB STATUS\G:

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)

Under titlen "SENESTE DETECTED DEADLOCK" kan vi se detaljer om vores deadlock.

For at se detaljerne om deadlock i mysql fejlloggen, skal vi aktivere indstillingen innodb_print_all_deadlocks i vores database.

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

MySQL-logfejl:

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)

I betragtning af, hvad vi har lært ovenfor om, hvorfor deadlocks opstår, kan du se, at der ikke er meget, vi kan gøre på databasesiden for at undgå dem. I hvert fald, som DBA'er er det vores pligt faktisk at fange dem, analysere dem og give feedback til udviklerne.

Virkeligheden er, at disse fejl er specifikke for hver applikation, så du bliver nødt til at kontrollere dem en efter en, og der er ingen guide til at fortælle dig, hvordan du fejlfinder dette. Med dette i tankerne er der nogle ting, du kan kigge efter.

Tips til at undersøge og undgå dødvande

Søg efter langvarige transaktioner. Da låsene normalt holdes indtil slutningen af ​​en transaktion, jo længere transaktionen er, jo længere låser ressourcerne. Hvis det er muligt, så prøv at opdele langvarige transaktioner i mindre/hurtigere.

Nogle gange er det ikke muligt rent faktisk at opdele transaktionerne, så arbejdet bør fokusere på at forsøge at udføre disse operationer i en ensartet rækkefølge hver gang, så transaktioner danner veldefinerede køer og ikke går i lås.

En løsning, som du også kan foreslå, er at tilføje genforsøgslogik til applikationen (forsøg selvfølgelig først at løse det underliggende problem) på en måde, så applikationen, hvis der opstår et dødvande, kører de samme kommandoer igen.

Tjek de anvendte isolationsniveauer, nogle gange prøver du ved at ændre dem. Se efter kommandoer som SELECT FOR UPDATE og SELECT FOR SHARE, da de genererer eksplicitte låse, og evaluer, om de virkelig er nødvendige, eller du kan arbejde med et ældre øjebliksbillede af dataene. En ting, du kan prøve, hvis du ikke kan fjerne disse kommandoer, er at bruge et lavere isolationsniveau, såsom LÆS KOMMITTET.

Tilføj selvfølgelig altid velvalgte indekser til dine tabeller. Så skal dine forespørgsler scanne færre indeksposter og følgelig indstille færre låse.

På et højere niveau kan du som DBA tage nogle forholdsregler for at minimere låsning generelt. For at nævne et eksempel, i dette tilfælde for PostgreSQL, kan du undgå at tilføje en standardværdi i den samme kommando, som du vil tilføje en kolonne. Ændring af en tabel vil få en virkelig aggressiv lås, og indstilling af en standardværdi for den vil faktisk opdatere de eksisterende rækker, der har nulværdier, hvilket gør at denne operation tager rigtig lang tid. Så hvis du opdeler denne operation i flere kommandoer, tilføjer kolonnen, tilføjer standardværdierne, opdaterer nulværdierne, vil du minimere låsevirkningen.

Selvfølgelig er der masser af tips som dette, som DBA'erne får med øvelsen (oprette indekser samtidigt, oprette pk-indekset separat før tilføjelse af pk'en, og så videre), men det vigtige er at lære og forstå denne "måde at tænker" og altid for at minimere låsepåvirkningen af ​​de operationer, vi udfører.

Oversigt

Forhåbentlig har denne blog givet dig nyttige oplysninger om låsninger i databasen, og hvordan du kan overvinde dem. Da der ikke er en sikker måde at undgå dødvande, kan det hjælpe dig med at fange dem, før de gør skade på dine databaseforekomster, hvis du ved, hvordan de fungerer. Softwareløsninger som ClusterControl kan hjælpe dig med at sikre, at dine databaser altid forbliver i form. ClusterControl har allerede hjulpet hundredvis af virksomheder - bliver din næste? Download din gratis prøveversion af ClusterControl i dag for at se, om den passer til dine databasebehov.


  1. Sådan indstilles standardsproget for alle nye logins i SQL Server (T-SQL)

  2. Hvordan fungerer PostgreSQL security_barrier views?

  3. Leverer hurtigere innovation til MariaDBs fællesskab

  4. Oprettelse af en Java-applikation i Oracle JDeveloper, del 2