Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Entendendo os Deadlocks no MySQL e PostgreSQL


Trabalhando com bancos de dados, o controle de simultaneidade é o conceito que garante que as transações do banco de dados sejam executadas simultaneamente sem violar a integridade dos dados.

Há muita teoria e diferentes abordagens em torno desse conceito e como realizá-lo, mas vamos nos referir brevemente à maneira como o PostgreSQL e o MySQL (ao usar o InnoDB) lidam com isso e um problema comum que pode surgir em sistemas altamente simultâneos:impasses.

Esses mecanismos implementam o controle de simultaneidade usando um método chamado MVCC (Multiversion Concurrency Control). Nesse método, quando um item está sendo atualizado, as alterações não substituirão os dados originais, mas uma nova versão do item (com as alterações) será criada. Assim teremos várias versões do item armazenadas.

Uma das principais vantagens desse modelo é que os bloqueios adquiridos para consulta (leitura) de dados não entram em conflito com bloqueios adquiridos para gravação de dados, e assim a leitura nunca bloqueia a gravação e a gravação nunca bloqueia a leitura.

Mas, se várias versões do mesmo item forem armazenadas, qual versão dele uma transação verá? Para responder a essa pergunta, precisamos revisar o conceito de isolamento de transação. As transações especificam um nível de isolamento, que define o grau em que uma transação deve ser isolada de modificações de recursos ou dados feitas por outras transações. Este grau está diretamente relacionado com o bloqueio gerado por uma transação e, portanto, como pode ser especificado no nível da transação, pode determinar o impacto que uma transação em execução pode ter sobre outras transações em execução.

Este é um tópico muito interessante e longo, embora não entraremos em muitos detalhes neste blog. Recomendamos a documentação oficial do PostgreSQL e MySQL para leitura adicional sobre este tópico.

Então, por que estamos abordando os tópicos acima ao lidar com deadlocks? Porque os comandos sql irão adquirir automaticamente os bloqueios para garantir o comportamento do MVCC, e o tipo de bloqueio adquirido depende do isolamento da transação definido.

Existem vários tipos de bloqueios (mais uma vez outro tópico longo e interessante para revisar para PostgreSQL e MySQL), mas, o importante sobre eles, é como eles interagem (mais exatamente, como eles entram em conflito) entre si. Por que é que? Porque duas transações não podem manter bloqueios de modos conflitantes no mesmo objeto ao mesmo tempo. E um detalhe não menor, uma vez adquirido, um bloqueio normalmente é mantido até o final da transação.

Este é um exemplo do PostgreSQL de como os tipos de bloqueio entram em conflito entre si:
Conflito de tipos de bloqueio do PostgreSQL
E para o MySQL:
Conflito de tipos de bloqueio do MySQL

X=bloqueio exclusivo         IX=bloqueio exclusivo de intenção
S=bloqueio compartilhado         IS=bloqueio compartilhado por intenção
Então, o que acontece quando tenho duas transações em execução que desejam manter bloqueios conflitantes no mesmo objeto ao mesmo tempo? Um deles pegará o cadeado e o outro terá que esperar.

Portanto, agora estamos em condições de entender verdadeiramente o que está acontecendo durante um impasse.

O que é um impasse então? Como você pode imaginar, existem várias definições para um impasse de banco de dados, mas eu gosto do seguinte por sua simplicidade.

Um deadlock de banco de dados é uma situação em que duas ou mais transações estão esperando uma pela outra para desistir de bloqueios.

Assim, por exemplo, a seguinte situação nos levará a um impasse:
Exemplo de deadlock
Aqui, o aplicativo A obtém um bloqueio na linha 1 da tabela 1 para fazer uma atualização.

Ao mesmo tempo, o aplicativo B obtém um bloqueio na tabela 2, linha 2.

Agora o aplicativo A precisa obter um bloqueio na tabela 2 linha 2, para continuar a execução e concluir a transação, mas não pode obter o bloqueio porque está retido pelo aplicativo B. O aplicativo A precisa aguardar o aplicativo B liberá-lo .

Mas o aplicativo B precisa obter um bloqueio na tabela 1 linha 1, para continuar a execução e finalizar a transação, mas não pode obter o bloqueio porque está retido pelo aplicativo A.

Então, aqui estamos em uma situação de impasse. O aplicativo A está aguardando o recurso retido pelo aplicativo B para ser concluído e o aplicativo B está aguardando o recurso retido pelo aplicativo A. Então, como continuar? O mecanismo de banco de dados detectará o deadlock e matará uma das transações, desbloqueando a outra e gerando um erro de deadlock na morta.

Vamos verificar alguns exemplos de deadlock do PostgreSQL e MySQL:

PostgreSQL


Suponha que tenhamos um banco de dados de teste com informações dos países do mundo.
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)

Temos duas sessões que desejam fazer alterações no banco de dados.

A primeira sessão modificará o campo de região para o código NLD e o campo de população para o código AUS.

A segunda sessão modificará o campo de região para o código AUS e o campo de população para o código NLD.

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

Sessão 1:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Europe' WHERE code='NLD';
UPDATE 1

Sessão 2:
world=# BEGIN;
BEGIN
world=# UPDATE country SET region='Oceania' WHERE code='AUS';
UPDATE 1
world=# UPDATE country SET population=15864001 WHERE code='NLD';

A Sessão 2 será travada aguardando a conclusão da Sessão 1.

Sessão 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"

Aqui temos nosso impasse. O sistema detectou o deadlock e eliminou a sessão 1.

Sessão 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

E podemos verificar se a segunda sessão terminou corretamente depois que o deadlock foi detectado e a Sessão 1 foi encerrada (assim, o bloqueio foi liberado).

Para ter mais detalhes podemos ver o log em nosso servidor 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

Aqui poderemos ver os comandos reais que foram detectados no deadlock.
Baixe o whitepaper hoje PostgreSQL Management &Automation with ClusterControlSaiba o que você precisa saber para implantar, monitorar, gerenciar e dimensionar o PostgreSQLBaixe o whitepaper

MySQL


Para simular um deadlock no MySQL, podemos fazer o seguinte.

Assim como no PostgreSQL, suponha que temos um banco de dados de teste com informações sobre atores e filmes, entre outras coisas.
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)

Temos dois processos que desejam fazer alterações no banco de dados.

O primeiro processo modificará o campo first_name para actor_id 1 e o campo last_name para actor_id 7.

O segundo processo modificará o campo first_name para actor_id 7 e o campo last_name para actor_id 1.

Dados da tabela:
actor_id: 1
first_name: PENELOPE
last_name: GUINESS
actor_id: 7
first_name: GRACE
last_name: MOSTEL

Sessão 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

Sessão 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';

A Sessão 2 será travada aguardando a conclusão da Sessão 1.

Sessão 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

Aqui temos nosso impasse. O sistema detectou o deadlock e eliminou a sessão 1.

Sessão 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

Como podemos ver no erro, como vimos para o PostgreSQL, existe um impasse entre os dois processos.

Para mais detalhes podemos usar o comando 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)

Sob o título "ÚLTIMO DEADLOCK DETECTADO", podemos ver detalhes do nosso deadlock.

Para ver o detalhe do deadlock no log de erros do mysql, devemos habilitar a opção innodb_print_all_deadlocks em nosso banco de dados.
mysql> set global innodb_print_all_deadlocks=1;
Query OK, 0 rows affected (0.00 sec)

Erro de registro do 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)

Levando em conta o que aprendemos acima sobre por que os deadlocks acontecem, você pode ver que não há muito o que fazer no lado do banco de dados para evitá-los. De qualquer forma, como DBAs, é nosso dever capturá-los, analisá-los e fornecer feedback aos desenvolvedores.

A realidade é que esses erros são específicos para cada aplicativo, portanto, você precisará verificá-los um por um e não há guia para informar como solucionar isso. Tendo isso em mente, há algumas coisas que você pode procurar.

Dicas para investigar e evitar impasses


Pesquise transações de longa duração. Como os bloqueios geralmente são mantidos até o final de uma transação, quanto mais longa a transação, mais longos serão os bloqueios sobre os recursos. Se for possível, tente dividir as transações de longa duração em menores/mais rápidas.

Às vezes, não é possível realmente dividir as transações, portanto, o trabalho deve se concentrar em tentar executar essas operações em uma ordem consistente a cada vez, para que as transações formem filas bem definidas e não ocorram deadlock.

Uma solução alternativa que você também pode propor é adicionar lógica de repetição ao aplicativo (é claro, tente resolver o problema subjacente primeiro) de forma que, se ocorrer um impasse, o aplicativo execute os mesmos comandos novamente.

Verifique os níveis de isolamento usados, às vezes você tenta alterando-os. Procure comandos como SELECT FOR UPDATE e SELECT FOR SHARE, pois eles geram bloqueios explícitos e avalie se eles são realmente necessários ou você pode trabalhar com um instantâneo mais antigo dos dados. Uma coisa que você pode tentar se não conseguir remover esses comandos é usar um nível de isolamento mais baixo, como READ COMMITTED.

Claro, sempre adicione índices bem escolhidos às suas tabelas. Em seguida, suas consultas precisam varrer menos registros de índice e, consequentemente, definir menos bloqueios.

Em um nível mais alto, como DBA, você pode tomar algumas precauções para minimizar o bloqueio em geral. Para citar um exemplo, neste caso para o PostgreSQL, você pode evitar adicionar um valor padrão no mesmo comando em que adicionará uma coluna. Alterar uma tabela obterá um bloqueio realmente agressivo e definir um valor padrão para ela atualizará as linhas existentes que possuem valores nulos, tornando essa operação muito longa. Portanto, se você dividir essa operação em vários comandos, adicionando a coluna, adicionando o padrão, atualizando os valores nulos, você minimizará o impacto do bloqueio.

É claro que existem muitas dicas como essa que os DBAs obtêm com a prática (criar índices simultaneamente, criar o índice pk separadamente antes de adicionar o pk e assim por diante), mas o importante é aprender e entender essa "forma de pensando" e sempre para minimizar o impacto do bloqueio das operações que estamos fazendo.

Resumo


Esperamos que este blog tenha fornecido informações úteis sobre impasses de banco de dados e como superá-los. Como não há uma maneira infalível de evitar deadlocks, saber como eles funcionam pode ajudá-lo a pegá-los antes que eles prejudiquem suas instâncias de banco de dados. Soluções de software como ClusterControl podem ajudá-lo a garantir que seus bancos de dados permaneçam sempre em forma. O ClusterControl já ajudou centenas de empresas - a sua será a próxima? Faça o download de sua avaliação gratuita do ClusterControl hoje mesmo para ver se é o ajuste certo para suas necessidades de banco de dados.