Po przeczytaniu trochę więcej odkryłem, że ponieważ InnoDB używa blokowania na poziomie wiersza, zakleszczenia mogą wystąpić podczas wstawiania lub aktualizowania pojedynczego wiersza, ponieważ akcje nie są atomowe. Biegłem:
SHOW ENGINE INNODB STATUS
znaleźć informacje na temat ostatniego impasu. Znalazłem:
------------------------
LATEST DETECTED DEADLOCK
------------------------
140106 17:22:41
*** (1) TRANSACTION:
TRANSACTION 63EB5222A, ACTIVE 0 sec starting index read
mysql tables in use 3, locked 3
LOCK WAIT 9 lock struct(s), heap size 3112, 6 row lock(s), undo log entries 2
MySQL thread id 4304350, OS thread handle 0x7fd3b74d3700, query id 173460207 192.168.0.2 sharecash Updating
UPDATE `click_rollups` SET `clicks` = `clicks` + 1, `last_updated` = '1389046961' WHERE `camp_id` = '27739' AND `country` = 'US' AND `clicks` < '1000' AND `time_created` = '1389046866'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 186 page no 407 n bits 1272 index `country` of table `sharecash`.`click_rollups` trx id 63EB5222A lock_mode X waiting
*** (2) TRANSACTION:
TRANSACTION 63EB52225, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
177 lock struct(s), heap size 31160, 17786 row lock(s), undo log entries 2
MySQL thread id 4304349, OS thread handle 0x7fd6961c8700, query id 173460194 192.168.0.1 sharecash Updating
UPDATE `click_rollups` SET `clicks` = `clicks` + 1, `last_updated` = '1389046961' WHERE `camp_id` = '30949' AND `country` = 'US' AND `clicks` < '1000' AND `time_created` = '1388964767'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 186 page no 407 n bits 1272 index `country` of table `sharecash`.`click_rollups` trx id 63EB52225 lock_mode X
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 186 page no 512 n bits 384 index `PRIMARY` of table `sharecash`.`click_rollups` trx id 63EB52225 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)
Widać, że dwa zapytania, które powodują zakleszczenie, są w rzeczywistości dokładnie tymi samymi zapytaniami. Pokazuje, że w klauzuli WHERE są też różne parametry dla kolumn, więc rzeczywiste wiersze, które są blokowane, są różne, co wydawało mi się nieco sprzeczne z intuicją - jak operacje na różnych zestawach wierszy mogą spowodować zakleszczenie?
Odpowiedź wydaje się być taka, że impas wynika z blokowania wpisów silnika zapytań w strukturach indeksowania. Jeśli spojrzysz na powyższe dane wyjściowe, zobaczysz, że jedna transakcja ma blokadę w określonej części określonej strony w country
indeksu i wymaga blokady na części indeksu klucza podstawowego, podczas gdy druga transakcja jest zasadniczo odwrotna.
Niezmiennikiem w tej części naszej aplikacji jest to, że tylko jeden wiersz miałby mniej niż 1000 kliknięć, więc wierzę, że poprzez naprawienie tego problemu problem zakleszczenia zostanie zminimalizowany, ponieważ będzie ogólnie mniej blokowanych. Dokumentacja MySQL sugeruje kodowanie aplikacji, aby zawsze ponownie wystawiać transakcje w przypadku wycofania z powodu zakleszczenia, co zapobiegłoby temu problemowi powodującemu błędy stron. Jeśli jednak ktoś ma inne pomysły, jak faktycznie uniknąć tych impasów, ponownie, opublikuj je w komentarzach!
EDYTUJ -
country
indeks nie musiał być używany przez transakcję, jak dla każdego camp_id
wartość była tylko garstka (zwykle tylko 1) różnych wartości country
, z których każdy odpowiadał tylko jednemu wierszowi. Dodałem wskazówkę dotyczącą indeksu do zapytania, aby przestało używać tego indeksu, a problem został rozwiązany bez żadnego spadku wydajności (prawdopodobnie niewielki zysk).