Jeśli stosujemy transakcje możemy się natknąć na następujący błąd:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Co to za dziwo ten deadlock i co się z nim robi?

Jak wiemy, InnoDB stosuje locki na poziomie rekordów. Ma to na celu zwiększenie wydajności przy dużej ilości zapytań odczyt/zapis. W przeciwieństwie do MyISAM, w przypadku InnoDB do danej tabeli może pisać większa ilość zapytań jednocześnie. Wiemy też, że InnoDB stosuje różne mechanizmy izolacji transakcji. Mają one na celu zapewnienie stabilnego “widoku” bazy dla danej transakcji. Chcemy aby dane, na których pracujemy nie zmieniały się w zależności od tego, co się dzieje w tle. Nie zawsze jest to możliwe do połączenia z odpowiednią wydajnością, ale chcemy mieć kontrolę nad tym i chcemy wiedzieć czego się możemy po danych spodziewać.

Ok, wracamy do naszych deadlocków. Dlaczego powstają? Mamy sobie taką tabelę:

mysql> SHOW CREATE TABLE tab\G
*************************** 1. row ***************************
Table: tab
Create Table: CREATE TABLE `tab` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
KEY `idx_a_b` (`a`,`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin2
1 row in set (0,00 sec)

mysql> SELECT * FROM tab;
+------+------+------+
| a    | b    | c    |
+------+------+------+
|    1 |    1 |    0 |
|    1 |    2 |    5 |
|    2 |    1 |    2 |
|    2 |    2 |    4 |
+------+------+------+
4 rows in set (0,00 sec)


Otwieramy dwa połączenia do bazy i rozpoczynamy dwie transakcje.

Pierwsza:

[cce]
mysql> BEGIN;
Query OK, 0 rows affected (0,00 sec)

mysql> UPDATE tab SET c=10 WHERE a=1 AND b=1;
Query OK, 0 rows affected (0,00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

Druga:

mysql> BEGIN;
Query OK, 0 rows affected (0,00 sec)

mysql> UPDATE tab SET c=3 WHERE a=1 AND b=2;
Query OK, 1 row affected (0,00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Teraz wykonujemy kolejny UPDATE w pierwszej transakcji:

mysql> UPDATE tab SET c=4 WHERE a=1 AND b=2;

MySQL wisi i czeka na zwolnienie się locka na ten rekord. Lock ten jest w posiadaniu drugiej transakcji. Właśnie tu, w drugiej transakcji próbujemy wykonać kolejny UPDATE:

mysql> UPDATE tab SET c=37 WHERE a=1 AND b=1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

MySQL wykrył że doszło do deadlocka – obie transakcje mają locki na rekordzie, który ma zostać zmodyfikowany przez tą drugą transakcję. Locki te nie zostaną zwolnione do póki transakcja się nie zakończy, a transakcja się nie zakończy dopóki locki nie zostaną zwolnione. W takiej sytuacji jedna z transakcji musi zostać zerwana, co też się stało.

Jeśli sami wywołujemy taki błąd, wiemy dlaczego tak się stało. Jak możemy dowiedzieć się więcej o tym co zaszło jeśli nie do końca znamy przyczynę? Pomoże nam w tym zapytanie SHOW ENGINE INNODB STATUS a dokładniej następująca jego sekcja:

------------------------
LATEST DETECTED DEADLOCK
------------------------
120316  8:05:26
*** (1) TRANSACTION:
TRANSACTION 291E, ACTIVE 29 sec, process no 1756, OS thread id 1246009680 starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1216, 4 row lock(s), undo log entries 1
MySQL thread id 618543, query id 9155344 localhost root Updating
UPDATE tab SET c=4 WHERE a=1 AND b=2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_a_b` of table `test`.`tab` trx id 291E lock_mode X waiting
*** (2) TRANSACTION:
TRANSACTION 291F, ACTIVE 25 sec, process no 1756, OS thread id 1092028752 starting index read
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1216, 4 row lock(s), undo log entries 1
MySQL thread id 618508, query id 9155375 localhost root Updating
UPDATE tab SET c=37 WHERE a=1 AND b=1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_a_b` of table `test`.`tab` trx id 291F lock_mode X
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_a_b` of table `test`.`tab` trx id 291F lock_mode X waiting
*** WE ROLL BACK TRANSACTION (2)

Widzimy wyraźnie o jakich transakcjach jest mowa, widzimy konkretne zapytania. Widzimy która transakcja jakie locki trzymała. To daje nam podstawy do przeanalizowania sytuacji i podjęcia próby uniknięcia takiego deadlocka na przyszłość. Niestety, nie zawsze sprawa jest tak oczywista jak powyżej. Często zdarzyć się może, że zapytania wcale nie wyglądają na powiązane w jakikolwiek sposób ze sobą. To może być efektem mechanizmu lockowania rekordów stosowanego przez InnoDB. Tak na prawdę to lock ustawiany nie jest na rekord, który jest doczytywany czy modyfikowany. Lock jest ustawiany na każdy rekord, który był odczytywany w ramach danego zapytania. Dlatego też w przypadku InnoDB bardzo ważna jest dobra indeksacja. Gdyby w naszym przykładzie zabrakło indeksów na kolumny a i b, to aby wykonać zapytanie:

UPDATE tab SET c=10 WHERE a=1 AND b=1;

MySQL musiałby przeskanować całą tabelę tab aby znaleźć odpowiednie rekordy. W praktyce równałoby się to lockowi na całą tabelę. W tej sytuacji druga transakcja nie byłaby w stanie wykonać nawet pierwszego UPDATE.

Co jeszcze ważne, jeśli chodzi o deadlocki, to to że one są. Są i będą istnieć. To jest nieodłączny element transakcyjnego silnika. Nie da się ich w żaden sposób uniknąć. Tak na prawdę, to nie są one groźne – jedyna szkoda to mniejsza wydajność ze względu na fakt że niektóre transakcje trzeba ponawiać. Jasne, jeśli to powoduje że całość aplikacji działa wolno, to jest to coś do poprawienia. Jeśli jednak wydajność nie stanowi problemu, deadlock też nie powinien. Trzeba po prostu pamiętać o ich obsłudze gdy piszemy aplikację. Gdy dostajemy informację o wystąpieniu deadlocka, bądź też otrzymamy komunikat o fakcie, że nasza transakcja została cofnięta ze względu na timeout (MySQL nie zawsze jest w stanie wykryć wystąpienie deadlocka – w tej sytuacji transakcja zostanie cofnięta po upływie timeoutu), trzeba to obsłużyć. Na przykład ponawiając wykonanie transakcji.

Tak więc, nie taki deadlock straszny jak go malują. Ważne aby być świadomym co to jest i w jaki sposób można go oswoić.