Nachdem ich etwas mehr gelesen hatte, stellte ich fest, dass, da InnoDB Sperren auf Zeilenebene verwendet, Deadlocks auftreten können, wenn nur eine einzelne Zeile eingefügt oder aktualisiert wird, da die Aktionen nicht atomar sind. Ich lief:
SHOW ENGINE INNODB STATUS
um Informationen zum letzten Deadlock zu finden. Ich habe gefunden:
------------------------
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)
Sie können sehen, dass die beiden Abfragen, die die Deadlocks verursachen, genau die gleichen sind. Es zeigt, dass es auch unterschiedliche Parameter für die Spalten in der WHERE-Klausel gibt, sodass die tatsächlichen Zeilen, die gesperrt werden, unterschiedlich sind, was mir etwas kontraintuitiv erschien - wie könnten Operationen auf verschiedenen Zeilensätzen einen Deadlock verursachen?
Die Antwort scheint zu sein, dass der Deadlock dadurch entsteht, dass die Abfrage-Engine Einträge in den Indizierungsstrukturen sperrt. Wenn Sie sich die obige Ausgabe ansehen, können Sie sehen, dass eine Transaktion einen bestimmten Teil einer bestimmten Seite im country
gesperrt hat index und benötigt eine Sperre für einen Teil des Primärschlüsselindex, während die andere Transaktion im Wesentlichen das Gegenteil ist.
Eine Konstante in diesem Teil unserer App, dass nur eine Zeile jemals weniger als 1000 Klicks haben würde, also glaube ich, dass durch die Behebung dieses Problems das Deadlock-Problem minimiert wird, da insgesamt weniger Sperren durchgeführt würden. Die MySQL-Dokumentation schlägt vor, Ihre Anwendungen so zu codieren, dass Transaktionen im Falle eines Rollbacks aufgrund eines Deadlocks immer neu ausgegeben werden, was verhindern würde, dass dieses Problem Seitenfehler verursacht. Wenn jedoch noch jemand andere Ideen hat, wie man diese Deadlocks tatsächlich vermeiden kann, postet sie bitte in den Kommentaren!
BEARBEITEN -
Das country
index musste nicht von der Transaktion verwendet werden, wie für jede camp_id
value gab es nur eine Handvoll (normalerweise nur 1) verschiedene Werte von country
, die jeweils nur einer Zeile entsprachen. Ich habe der Abfrage einen Indexhinweis hinzugefügt, damit sie diesen Index nicht mehr verwendet, und das Problem ist jetzt ohne Leistungseinbußen behoben (wahrscheinlich ein kleiner Gewinn).