さらに読んだ後、InnoDBは行レベルのロックを使用するため、アクションはアトミックではないため、単一の行を挿入または更新するだけでデッドロックが発生する可能性があることがわかりました。実行しました:
SHOW ENGINE INNODB STATUS
最後のデッドロックに関する情報を検索します。見つけた:
------------------------
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)
デッドロックを引き起こしている2つのクエリは、実際にはまったく同じものであることがわかります。これは、WHERE句の列にもさまざまなパラメーターがあることを示しているため、ロックされている実際の行は異なります。これは、私には少し直感に反しているように見えました。さまざまな行のセットを操作すると、デッドロックが発生する可能性があります。
答えは、デッドロックがインデックス構造のエントリをロックするクエリエンジンから生じているようです。上記の出力を見ると、1つのトランザクションがcountry
の特定のページの特定の部分でロックされていることがわかります。 インデックスを作成し、主キーインデックスの一部をロックする必要がありますが、他のトランザクションは基本的に逆の場合です。
アプリのこの部分では、クリック数が1000回未満になるのは1行だけであるという不変条件です。この問題を修正することで、ロックが全体的に少なくなるため、デッドロックの問題が最小限に抑えられると思います。 MySQLのドキュメントでは、デッドロックが原因でロールバックが発生した場合に常にトランザクションを再発行するようにアプリケーションをコーディングすることを推奨しています。これにより、ページでエラーが発生するこの問題を防ぐことができます。ただし、これらのデッドロックを実際に回避する方法について他にアイデアがある場合は、コメントに投稿してください。
編集-
country
各camp_id
のように、インデックスをトランザクションで使用する必要はありませんでした 値country
にはほんの一握り(通常は1つ)の異なる値しかありませんでした 、それぞれが1つの行にのみ対応していました。クエリにインデックスヒントを追加して、このインデックスの使用を停止しました。この問題は、パフォーマンスに影響を与えることなく修正されました(おそらくわずかな増加)。