sql >> Databáze >  >> RDS >> Mysql

Pochopení uváznutí v MySQL a PostgreSQL

Při práci s databázemi je kontrola souběžnosti konceptem, který zajišťuje, že databázové transakce jsou prováděny souběžně bez narušení integrity dat.

Kolem tohoto konceptu a toho, jak toho dosáhnout, existuje mnoho teorií a různých přístupů, ale stručně se zmíníme o způsobu, jakým to PostgreSQL a MySQL (při použití InnoDB) zpracovávají, a o běžném problému, který může nastat ve vysoce souběžných systémech:uváznutí.

Tyto motory implementují řízení souběžnosti pomocí metody zvané MVCC (Multiversion Concurrency Control). Při této metodě, když je položka aktualizována, změny nepřepíší původní data, ale místo toho se vytvoří nová verze položky (se změnami). Budeme tak mít uloženo několik verzí položky.

Jednou z hlavních výhod tohoto modelu je, že zámky získané pro dotazování (čtení) dat nejsou v konfliktu se zámky získanými pro zápis dat, takže čtení nikdy neblokuje zápis a zápis nikdy neblokuje čtení.

Pokud je však uloženo několik verzí stejné položky, jakou její verzi transakce uvidí? Abychom na tuto otázku odpověděli, musíme přezkoumat koncept izolace transakcí. Transakce určují úroveň izolace, která definuje míru, do jaké musí být jedna transakce izolována od úprav zdrojů nebo dat provedených jinými transakcemi. Tento stupeň přímo souvisí s uzamčením generovaným transakcí, a tak, jak může být specifikován na úrovni transakce, může určovat dopad, který může mít probíhající transakce na ostatní probíhající transakce.

Toto je velmi zajímavé a dlouhé téma, i když se v tomto blogu nebudeme zabývat příliš mnoha podrobnostmi. Pro další čtení na toto téma doporučujeme oficiální dokumentaci PostgreSQL a MySQL.

Proč se tedy při řešení patových situací zabýváme výše uvedenými tématy? Protože příkazy sql automaticky získávají zámky, aby bylo zajištěno chování MVCC, a získaný typ zámku závisí na definované izolaci transakcí.

Existuje několik typů zámků (opět další dlouhé a zajímavé téma ke kontrole pro PostgreSQL a MySQL), ale důležité na nich je to, jak se vzájemně ovlivňují (přesněji jak jsou v konfliktu). proč tomu tak je? Protože dvě transakce nemohou držet zámky konfliktních režimů na stejném objektu ve stejnou dobu. A nepodstatný detail, jakmile je získán, je zámek normálně držen až do konce transakce.

Toto je příklad PostgreSQL, jak se typy zamykání vzájemně střetávají:

Konflikt typů zamykání PostgreSQL

A pro MySQL:

Konflikt typů zamykání MySQL

X=exkluzivní zámek         IX=záměrný výhradní zámek
S=sdílený zámek         IS=záměrný sdílený zámek

Co se tedy stane, když mám dvě spuštěné transakce, které chtějí současně držet konfliktní zámky na stejném objektu? Jeden z nich dostane zámek a druhý bude muset počkat.

Nyní jsme tedy v pozici, kdy skutečně rozumíme tomu, co se děje během patové situace.

Co je tedy uváznutí? Jak si dokážete představit, existuje několik definic pro zablokování databáze, ale následující se mi líbí pro svou jednoduchost.

Zablokování databáze je situace, ve které dvě nebo více transakcí na sebe čekají, aby se vzdaly uzamčení.

Takže například následující situace nás zavede do slepé uličky:

Příklad uváznutí

Zde aplikace A zablokuje tabulku 1 řádek 1, aby mohla provést aktualizaci.

Současně aplikace B získá zámek na tabulce 2 řádek 2.

Nyní aplikace A potřebuje získat zámek na tabulce 2 řádek 2, aby mohla pokračovat v provádění a dokončit transakci, ale nemůže získat zámek, protože jej drží aplikace B. Aplikace A musí počkat, až jej aplikace B uvolní. .

Ale aplikace B potřebuje získat zámek na tabulce 1 řádek 1, aby mohla pokračovat v provádění a dokončit transakci, ale nemůže získat zámek, protože je držena aplikací A.

Takže jsme v patové situaci. Aplikace A čeká na zdroj držený aplikací B, aby mohla být dokončena, a aplikace B čeká na zdroj držený aplikací A. Jak tedy pokračovat? Databázový stroj detekuje zablokování a ukončí jednu z transakcí, odblokuje druhou a u zabité vyvolá chybu uváznutí.

Podívejme se na některé příklady zablokování PostgreSQL a MySQL:

PostgreSQL

Předpokládejme, že máme testovací databázi s informacemi ze zemí světa.

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)

Máme dvě relace, které chtějí provést změny v databázi.

První relace upraví pole regionu pro kód NLD a pole populace pro kód AUS.

Druhá relace upraví pole regionu pro kód AUS a pole populace pro kód NLD.

Data tabulky:

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

Relace 1:

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

Relace 2:

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

Relace 2 se zastaví a čeká na dokončení relace 1.

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

Tady máme patovou situaci. Systém detekoval uváznutí a ukončil relaci 1.

Relace 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

A můžeme zkontrolovat, že druhá relace skončila správně poté, co bylo zjištěno uváznutí a relace 1 byla zabita (tedy zámek byl uvolněn).

Pro více podrobností se můžeme podívat na log na našem PostgreSQL serveru:

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

Zde budeme moci vidět skutečné příkazy, které byly detekovány při uváznutí.

Stáhněte si Whitepaper Today Správa a automatizace PostgreSQL s ClusterControlZjistěte, co potřebujete vědět k nasazení, monitorování, správě a škálování PostgreSQLStáhněte si Whitepaper

MySQL

Pro simulaci uváznutí v MySQL můžeme provést následující.

Stejně jako u PostgreSQL předpokládejme, že máme testovací databázi s informacemi o hercích a filmech mimo jiné.

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)

Máme dva procesy, které chtějí provést změny v databázi.

První proces upraví pole first_name pro actor_id 1 a pole last_name pro actor_id 7.

Druhý proces upraví pole first_name pro actor_id 7 a pole last_name pro actor_id 1.

Data tabulky:

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

Relace 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

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

Relace 2 se zastaví a čeká na dokončení relace 1.

Relace 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

Tady máme patovou situaci. Systém detekoval uváznutí a ukončil relaci 1.

Relace 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

Jak vidíme na chybě, jak jsme viděli u PostgreSQL, mezi oběma procesy došlo k uváznutí.

Pro další podrobnosti můžeme použít příkaz 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)

Pod názvem „LATEST DETECTED DEADLOCK“ můžeme vidět podrobnosti o našem uváznutí.

Chcete-li zobrazit podrobnosti o zablokování v protokolu chyb mysql, musíme v naší databázi povolit možnost innodb_print_all_deadlocks.

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

Chyba protokolu MySQL:

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)

Vezmeme-li v úvahu to, co jsme se dozvěděli výše o tom, proč dochází k uváznutí, můžete vidět, že na straně databáze nemůžeme udělat mnoho, abychom se jim vyhnuli. Jako správci databází je každopádně naší povinností je skutečně zachytit, analyzovat a poskytnout zpětnou vazbu vývojářům.

Skutečností je, že tyto chyby jsou specifické pro každou aplikaci, takže je budete muset zkontrolovat jednu po druhé a neexistuje žádný průvodce, který by vám řekl, jak tento problém vyřešit. Mějte to na paměti, existuje několik věcí, které můžete hledat.

Tipy pro vyšetřování a předcházení uváznutí

Vyhledávejte dlouhotrvající transakce. Protože zámky jsou obvykle drženy až do konce transakce, čím delší je transakce, tím delší jsou zámky nad zdroji. Pokud je to možné, zkuste rozdělit dlouhotrvající transakce na menší/rychlejší.

Někdy není možné transakce skutečně rozdělit, takže by se práce měla zaměřit na pokusy o provedení těchto operací pokaždé v konzistentním pořadí, aby transakce tvořily dobře definované fronty a neuvázly na mrtvém bodě.

Jedním z řešení, které můžete také navrhnout, je přidat do aplikace logiku opakování (samozřejmě se nejprve pokuste vyřešit základní problém) tak, že pokud dojde k uváznutí, aplikace bude znovu spouštět stejné příkazy.

Zkontrolujte používané úrovně izolace, někdy je zkuste změnit. Hledejte příkazy jako SELECT FOR UPDATE a SELECT FOR SHARE, protože generují explicitní zámky, a vyhodnoťte, zda jsou skutečně potřeba, nebo zda můžete pracovat se starším snímkem dat. Jedna věc, kterou můžete zkusit, pokud tyto příkazy nemůžete odstranit, je použití nižší úrovně izolace, jako je READ COMMITTED.

Do tabulek samozřejmě vždy přidávejte dobře zvolené indexy. Vaše dotazy pak potřebují prohledat méně indexových záznamů a následně nastavit méně zámků.

Na vyšší úrovni můžete jako DBA přijmout určitá opatření k minimalizaci zamykání obecně. Abychom uvedli jeden příklad, v tomto případě pro PostgreSQL se můžete vyhnout přidávání výchozí hodnoty do stejného příkazu, do kterého přidáváte sloupec. Změnou tabulky získáte skutečně agresivní zámek a nastavením výchozí hodnoty se ve skutečnosti aktualizují existující řádky, které mají hodnoty null, takže tato operace bude trvat opravdu dlouho. Pokud tedy tuto operaci rozdělíte na několik příkazů, přidáte sloupec, přidáte výchozí, aktualizujete hodnoty null, minimalizujete dopad zamykání.

Samozřejmě existuje spousta tipů, jako je tento, které DBA získávají praxí (současné vytváření indexů, vytváření indexu pk samostatně před přidáním pk atd.), ale důležité je naučit se a pochopit tento „způsob myšlení“ a vždy minimalizovat dopad operací, které provádíme.

Shrnutí

Doufejme, že vám tento blog poskytl užitečné informace o zablokování databáze a o tom, jak je překonat. Protože neexistuje spolehlivý způsob, jak se vyhnout patovým situacím, znalost jejich fungování vám může pomoci je zachytit dříve, než poškodí instance vaší databáze. Softwarová řešení, jako je ClusterControl, vám mohou pomoci zajistit, aby vaše databáze zůstaly vždy ve tvaru. ClusterControl již pomohl stovkám podniků – bude ten váš další? Stáhněte si bezplatnou zkušební verzi ClusterControl ještě dnes a zjistěte, zda se hodí pro vaše databázové potřeby.


  1. Doplňte řetězec úvodními nulami, aby měl v SQL Server 2008 3 znaky

  2. mysql SQL:konkrétní položka, která má být první, a potom seřadit zbývající položky

  3. Naučte se používat SQL SELECT s příklady

  4. Získávání zadaných výsledků z ActiveRecord raw SQL