Se você utiliza uma Storage Engine que suporta transações, você
provavelmente já enfrentou ou ouviu falar de
deadlock's
.
Na Documentação do MySQL podemos ver:
“Always be prepared to re-issue a transaction if it fails due to
deadlock. Deadlocks are not dangerous. Just try again.”
“Sempre esteja preparado para re-executar uma transação se ela
falhar por causa de deadlock. Deadlocks não são perigosos. Apenas
re-execute a transação.”
Na empresa em que trabalho, temos um importante processo que de
vez em quando falha por causa de deadlock. Decidi então melhorar
este processo e fazer o que a documentação diz (re-executar a
transação).
Para fazer isso, Eu queria poder reproduzir o scenario do
deadlock e a transação vitima do rollback teria que ser a do
processo em questão.
Criar um deadlock é simples, você só precisa de 2 sessões em que
cada uma esteja segurando o “lock” que a outra está esperando.
Por exemplo:
Temos uma tabela que possui 4 linhas (linha 1, linha 2, linha 3,
linha 4) e temos duas transações que fazem o seguinte:
T1: travar (lock) linha 1; T2: travar (lock) linha 4; T1: tentar travar linha 4 (este comando irá aguardar até que T2 destrave a linha 4); T2: tentar travar linha 1 (este comando irá aguardar até que T1 destrave a linha 1);
Neste momento, ambas transações estarão esperando entre si o destravamento das duas linhas e neste momento o MySQL irá detectar o deadlock. Vamor ver isso em prática:
T1 > CREATE TABLE t (i INT, PRIMARY KEY(i)) ENGINE = InnoDB; Query OK, 0 rows affected (0.08 sec) T1 > INSERT INTO t VALUES (1),(2),(3),(4); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0 T1 > START TRANSACTION; Query OK, 0 rows affected (0.00 sec) T1 > UPDATE t SET i = 1 WHERE i = 1; Query OK, 0 rows affected (0.01 sec) Rows matched: 1 Changed: 0 Warnings: 0 T2 > START TRANSACTION; Query OK, 0 rows affected (0.00 sec) T2 > UPDATE t SET i = 4 WHERE i = 4; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 T1 > UPDATE t SET i = 4 WHERE i = 4; Query OK, 0 rows affected (9.54 sec) Rows matched: 1 Changed: 0 Warnings: 0 T2 > UPDATE t SET i = 1 WHERE i = 1; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Como o MySQL escolhe a transação vitima do rollback ?
O MySQL faz um calculo interno baseado no “peso” da transação.
Este peso é calculado levando em consideração o numero de linhas
alterada e o numero de linhas travadas(locked) por cada
transação.
Este valor pode ser verificado na coluna trx_weight
da tabela information_schema.innodb_trx
:
T1 > SELECT * FROM information_schema.innodb_trx\G *************************** 1. row *************************** trx_id: 23326 trx_state: RUNNING trx_started: 2016-02-19 10:10:45 trx_requested_lock_id: NULL trx_wait_started: NULL trx_weight: 3 trx_mysql_thread_id: 5 trx_query: SELECT * FROM information_schema.innodb_trx trx_operation_state: NULL trx_tables_in_use: 0 trx_tables_locked: 1 trx_lock_structs: 3 trx_lock_memory_bytes: 1136 trx_rows_locked: 2 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: REPEATABLE READ trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 0 trx_is_read_only: 0 trx_autocommit_non_locking: 0 1 row in set (0.00 sec)
A Dica:
Um fator muito importante na hora do calculo acima, é se alguma
transação alterou dados em tabelas não-transacionais. Caso alguma
transação tenha alterado dados nestas tabelas, ela é considerada
mais pesada que as outras.
Para o meu teste, eu criei uma tabela MyISAM e inseri uma linha
nesta tabela para fazer ela “pesar mais”:
T2 > CREATE TABLE t2 (i INT, PRIMARY KEY(i)) ENGINE = MyISAM; Query OK, 0 rows affected (0.02 sec) T1 > START TRANSACTION; Query OK, 0 rows affected (0.00 sec) T1 > UPDATE t SET i = 1 WHERE i = 1; Query OK, 0 rows affected (0.01 sec) Rows matched: 1 Changed: 0 Warnings: 0 T2 > START TRANSACTION; Query OK, 0 rows affected (0.00 sec) T2 > UPDATE t SET i = 4 WHERE i = 4; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 T2 > INSERT INTO t2 VALUES (1); Query OK, 1 row affected (0.00 sec) T1 > UPDATE t SET i = 4 WHERE i = 4; -- este comando vai esperar até que T2 destrave a linha 4 ... T2 > UPDATE t SET i = 1 WHERE i = 1; Query OK, 0 rows affected (0.00 sec) Rows matched: 1 Changed: 0 Warnings: 0 T1 > ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Como vocês podem ver, T1 foi a vitima do rollback neste caso.
Por hoje é só pessoal. Espero que tenham gostado.