根据用户流量,我的Web应用程序可能最多每秒1-2次运行以下查询:
UPDATE `click_rollups`
SET `clicks` = `clicks` + 1, `last_updated` = ?
WHERE `camp_id` = ?
AND `country` = ?
AND `clicks` < ?
AND `time_created` = ?
Run Code Online (Sandbox Code Playgroud)
我们的日志显示有时会出现此错误:
SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction
Run Code Online (Sandbox Code Playgroud)
但是,click_rollups
在该事务的写上下文中仅使用一次,因此我无法想象会发生死锁的方式。仅使用SELECT
s 在应用程序中的其他位置仅查询了一次。
因此,这是否意味着来自这两个单独事务(更新和仅选择)的死锁导致了该问题,因为每个单独事务仅使用该表一次(使用该表的查询未引用任何其他表) )?还是可能存在行级锁定问题,这可能意味着其中一个事务可能因同一事务的其他出现而陷入僵局?
经过更多阅读后,我发现,由于InnoDB确实使用行级锁定,因此仅插入或更新单个行时会发生死锁,因为操作不是原子操作。我跑了:
SHOW ENGINE INNODB STATUS
Run Code Online (Sandbox Code Playgroud)
查找有关最后一个死锁的信息。我发现:
------------------------
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)
Run Code Online (Sandbox Code Playgroud)
您可以看到导致死锁的两个查询实际上是完全相同的。它表明WHERE子句中的列也有不同的参数,因此被锁定的实际行是不同的,这对我来说似乎有点违反直觉-在不同的行集上进行操作怎么会导致死锁?
答案似乎是死锁是由索引结构中的查询引擎锁定条目引起的。如果查看上面的输出,则可以看到一个事务在country
索引中某个页面的某个部分上具有锁定,而在主键索引的一部分上需要锁定,而另一事务本质上是相反的情况。
在我们应用程序的这一部分中不变的是,只有一行的点击次数将少于1000次,因此,我相信通过修复该问题,死锁问题将得到最小化,因为总体上,锁定的完成会更少。MySQL文档建议对您的应用程序进行编码,以使其在由于死锁而回滚的情况下始终重新发出事务,这可以防止此问题导致页面出错。但是,如果有人对如何真正避免这些死锁有任何其他想法,请再次在评论中发布它们!
编辑-
在country
没有需要索引由事务中使用,作为每个camp_id
值有只有极少数(通常只是1)的不同的值country
,其中的每一个仅相应于一个行。我已经在查询中添加了索引提示,以使其停止使用该索引,并且现在已解决了该问题,而没有任何性能下降(可能会有很小的收获)。