Mysql
 sql >> база данни >  >> RDS >> Mysql

Разбиране на застой в MySQL и PostgreSQL

При работа с бази данни контролът на паралелността е концепцията, която гарантира, че транзакциите на базата данни се изпълняват едновременно, без да се нарушава целостта на данните.

Има много теория и различни подходи около тази концепция и как да я постигнем, но ще разгледаме накратко начина, по който PostgreSQL и MySQL (когато се използва InnoDB) се справят с нея, както и често срещан проблем, който може да възникне в силно конкурентни системи:безизходица.

Тези двигатели реализират контрол на едновременност чрез използване на метод, наречен MVCC (Multiversion Concurrency Control). При този метод, когато даден елемент се актуализира, промените няма да презапишат оригиналните данни, а вместо това ще бъде създадена нова версия на елемента (с промените). Така ще имаме съхранени няколко версии на елемента.

Едно от основните предимства на този модел е, че заключванията, придобити за запитване (четене) на данни, не са в конфликт с заключванията, придобити за запис на данни, и така четенето никога не блокира записването, а записването никога не блокира четенето.

Но ако се съхраняват няколко версии на един и същи артикул, коя негова версия ще види транзакцията? За да отговорим на този въпрос, трябва да прегледаме концепцията за изолация на транзакциите. Транзакциите определят ниво на изолация, което определя степента, до която една транзакция трябва да бъде изолирана от модификации на ресурси или данни, направени от други транзакции. Тази степен е пряко свързана със заключването, генерирано от транзакция, и така, тъй като може да бъде определено на ниво транзакция, може да определи въздействието, което изпълняваща се транзакция може да има върху други текущи транзакции.

Това е много интересна и дълга тема, въпреки че няма да навлизаме в твърде подробности в този блог. Препоръчваме официалната документация на PostgreSQL и MySQL за допълнително четене по тази тема.

И така, защо навлизаме в горните теми, когато се занимаваме със застой? Тъй като sql командите автоматично ще придобиват заключвания, за да осигурят поведението на MVCC, а придобитият тип заключване зависи от дефинираната изолация на транзакцията.

Има няколко вида ключалки (отново друга дълга и интересна тема за преглед за PostgreSQL и MySQL), но важното при тях е как взаимодействат (най-точно как си противоречат) помежду си. Защо така? Тъй като две транзакции не могат да държат заключвания на конфликтни режими на един и същ обект по едно и също време. И незначителен детайл, веднъж придобит, обикновено се задържа до края на транзакцията.

Това е PostgreSQL пример за това как типовете заключване са в конфликт един с друг:

Конфликт между типовете заключване на PostgreSQL

И за MySQL:

Конфликт между типовете заключване на MySQL

X=изключително заключване         IX=изключително заключване на намерение
S=споделено заключване         IS=споделено заключване за намерение

И така, какво се случва, когато имам две текущи транзакции, които искат да държат конфликтни заключвания на един и същи обект едновременно? Един от тях ще получи ключалката, а другият ще трябва да изчака.

Така че сега сме в състояние наистина да разберем какво се случва по време на безизходица.

Тогава какво е задънена улица? Както можете да си представите, има няколко дефиниции за блокиране на база данни, но харесвам следното заради неговата простота.

Застой в базата данни е ситуация, при която две или повече транзакции чакат една друга, за да се откажат от заключванията.

Така например, следната ситуация ще ни доведе до задънена улица:

Пример за блокиране

Тук приложение A получава заключване на таблица 1, ред 1, за да направи актуализация.

В същото време приложение B получава заключване на таблица 2, ред 2.

Сега приложение A трябва да получи заключване на таблица 2, ред 2, за да продължи изпълнението и да завърши транзакцията, но не може да получи заключването, защото се държи от приложение B. Приложение A трябва да изчака приложение B да го освободи .

Но приложение B трябва да получи заключване на таблица 1, ред 1, за да продължи изпълнението и да завърши транзакцията, но не може да получи заключването, защото се държи от приложение A.

Така че тук сме в задънена улица. Приложение A чака ресурса, държан от приложение B, за да завърши, а приложение B чака ресурса, държан от приложение A. И така, как да продължим? Двигателят на базата данни ще открие блокирането и ще убие една от транзакциите, деблокирайки другата и ще издигне грешка в блокиране на убитата.

Нека проверим някои примери за блокиране на PostgreSQL и MySQL:

PostgreSQL

Да предположим, че имаме тестова база данни с информация от страните по света.

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)

Имаме две сесии, които искат да направят промени в базата данни.

Първата сесия ще промени полето за регион за NLD кода и полето за населението за AUS кода.

Втората сесия ще промени полето за регион за AUS кода и полето за населението за NLD кода.

Данни от таблицата:

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

Сесия 1:

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

Сесия 2:

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

Сесия 2 ще увисне в очакване да приключи сесия 1.

Сесия 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"

Тук имаме нашата безизходица. Системата откри блокирането и прекрати сесия 1.

Сесия 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

И можем да проверим дали втората сесия е завършила правилно, след като е била открита блокирането и сесия 1 е убита (по този начин заключването е освободено).

За повече подробности можем да видим лог в нашия PostgreSQL сървър:

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

Тук ще можем да видим действителните команди, които са били открити при застой.

Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга

MySQL

За да симулираме застой в MySQL, можем да направим следното.

Както при PostgreSQL, да предположим, че имаме тестова база данни с информация за актьори и филми, наред с други неща.

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)

Имаме два процеса, които искат да направят промени в базата данни.

Първият процес ще промени полето first_name за actor_id 1 и полето last_name за actor_id 7.

Вторият процес ще промени полето first_name за actor_id 7 и полето last_name за actor_id 1.

Данни от таблицата:

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

Сесия 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

Сесия 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';

Сесия 2 ще увисне в очакване да приключи сесия 1.

Сесия 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

Тук имаме нашата безизходица. Системата откри блокирането и прекрати сесия 1.

Сесия 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

Както можем да видим в грешката, както видяхме за PostgreSQL, има блокиране между двата процеса.

За повече подробности можем да използваме командата 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)

Под заглавието „ПОСЛЕДНАТА ОТКРИТА ЗАСТЪПКА“ можем да видим подробности за нашата безизходица.

За да видим подробностите за блокирането в дневника за грешки на mysql, трябва да активираме опцията innodb_print_all_deadlocks в нашата база данни.

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

Грешка в дневника на 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)

Като вземем предвид това, което научихме по-горе за това защо възникват задънки, можете да видите, че не можем да направим много от страна на базата данни, за да ги избегнем. Както и да е, като администратори на база данни, наше задължение е всъщност да ги хванем, да ги анализираме и да предоставим обратна връзка на разработчиците.

Реалността е, че тези грешки са специфични за всяко приложение, така че ще трябва да ги проверявате една по една и няма ръководство, което да ви каже как да отстраните това. Имайки това предвид, има някои неща, които можете да търсите.

Съвети за разследване и избягване на безизходица

Търсете дългосрочни транзакции. Тъй като заключванията обикновено се държат до края на транзакция, колкото по-дълга е транзакцията, толкова по-дълго са заключванията върху ресурсите. Ако е възможно, опитайте се да разделите дългосрочните транзакции на по-малки/по-бързи.

Понякога не е възможно реално да се разделят транзакциите, така че работата трябва да се съсредоточи върху опитите за изпълнение на тези операции в последователен ред всеки път, така че транзакциите да образуват добре дефинирани опашки и да не блокират.

Едно заобиколно решение, което можете също да предложите, е да добавите логика за повторен опит в приложението (разбира се, първо се опитайте да разрешите основния проблем) по начин, по който, ако възникне задънена улица, приложението ще изпълни отново същите команди.

Проверете използваните нива на изолация, понякога се опитвате да ги промените. Потърсете команди като SELECT FOR UPDATE и SELECT FOR SHARE, тъй като те генерират изрични заключвания и преценете дали наистина са необходими или можете да работите с по-стара моментна снимка на данните. Едно нещо, което можете да опитате, ако не можете да премахнете тези команди, е да използвате по-ниско ниво на изолация, като READ COMMITTED.

Разбира се, винаги добавяйте добре подбрани индекси към вашите таблици. Тогава вашите заявки трябва да сканират по-малко индексни записи и следователно да задават по-малко заключвания.

На по-високо ниво, като DBA, можете да вземете някои предпазни мерки, за да сведете до минимум заключването като цяло. За да назовем един пример, в този случай за PostgreSQL, можете да избегнете добавянето на стойност по подразбиране в същата команда, в която ще добавите колона. Промяната на таблица ще получи наистина агресивно заключване и задаване на стойност по подразбиране за нея всъщност ще актуализира съществуващите редове, които имат нулеви стойности, което прави тази операция много дълга. Така че, ако разделите тази операция на няколко команди, като добавите колоната, добавите по подразбиране, актуализирате нулевите стойности, ще сведете до минимум влиянието на заключването.

Разбира се, има много съвети като този, които администраторите на база данни получават с практиката (създаване на индекси едновременно, създаване индекса pk отделно преди добавяне на pk и т.н.), но важното е да научите и разберете този „начин на мислене" и винаги да минимизираме ефекта от заключването на операциите, които извършваме.

Резюме

Надяваме се, че този блог ви е предоставил полезна информация за блокирането на базата данни и как да ги преодолеете. Тъй като няма сигурен начин за избягване на блокиране, знаейки как работят, може да ви помогне да ги хванете, преди да навредят на вашите екземпляри от база данни. Софтуерни решения като ClusterControl могат да ви помогнат да гарантирате, че вашите бази данни винаги са във форма. ClusterControl вече помогна на стотици предприятия - ще бъде ли вашето следващо? Изтеглете безплатната си пробна версия на ClusterControl днес, за да видите дали е подходяща за нуждите на вашата база данни.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Разбиране на MySQL TRUNCATE TABLE чрез практически примери

  2. Преминаване от MySQL 5.7 към MySQL 8.0 - Какво трябва да знаете

  3. Инсталирайте Apache, MySQL 8 или MariaDB 10 и PHP 7 на CentOS 7

  4. MySQL MariaDB – Заявка с помощта на Temp Table

  5. Как мога да възстановя пълните привилегии на root потребителя на MySQL?