我有1,000张2种不同类型的优惠券。我想在我们的网站上给他们一些特殊用户。因此每个用户将获得每种类型的1张优惠券。 为此,我创建了一个表 t_voucher_pool :
CREATE TABLE `t_voucher_pool` (
`iAutoID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`iCrowdID` INT(10) UNSIGNED DEFAULT NULL,
`sTypeCode` VARCHAR(50) NOT NULL,
`sCode` VARCHAR(255) NOT NULL,
`sPassword` VARCHAR(255) NOT NULL,
`iBindStatus` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1',
`iVoucherID` INT(10) UNSIGNED DEFAULT NULL,
`iBindTime` INT(10) UNSIGNED DEFAULT NULL,
`iStatus` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1',
`iCreateTime` INT(10) UNSIGNED DEFAULT NULL,
`iUpdateTime` INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`iAutoID`),
KEY `idx_crowdid_typecode` (`iCrowdID`,`sTypeCode`,`iBindStatus`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
这是将在我的逻辑中执行的sql:
1.START TRANSACTION;
2.SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='LUCKYDRAW' LIMIT 1 FOR UPDATE; //get one available voucher for LUCKYDRAW
3.SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='JD' LIMIT 1 FOR UPDATE; //get one available voucher for JD
4.UPDATE t_voucher_pool SET iBindStatus=2 WHERE iAutoID=401; //update bindStatus for LUCKYDRAW voucher
5.UPDATE t_voucher_pool SET iBindStatus=2 WHERE iAutoID=10401; //update bindStatus for JD
6.COMMIT;
当并发级别为1时,此工作正常。但是当并发级别提高时,我发现某些请求将失败并且它们是由死锁引起的。如果第2步 第1步,会话1 尝试执行第5步,则会检测到死锁
以下是重现死锁的序列:
session1>>START TRANSACTION;
session1>>SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='LUCKYDRAW' LIMIT 1 FOR UPDATE;
session2>>START TRANSACTION;
session2>>SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='LUCKYDRAW' LIMIT 1 FOR UPDATE;(pending)
session1>>SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='JD' LIMIT 1 FOR UPDATE;
session1>>UPDATE t_voucher_pool SET iBindStatus=2 WHERE iAutoID=401;
session1>>UPDATE t_voucher_pool SET iBindStatus=2 WHERE iAutoID=10401;
session2>>ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
我猜对字段bindStatus的更新导致了这种死锁。但是,由于我已经在第3步上获得了此锁定的X锁定,为什么还会发生这种情况?
对此有任何详细解释将非常感谢。感谢。
2015/3/6更新
对不起家伙,看来我之前发布的例子无法重现问题。我刚刚更新了示例,这里是innodb状态的信息:2015-03-06 09:10:53 7f975a02b700
*** (1) TRANSACTION:
TRANSACTION 264943793, ACTIVE 44 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 6608579, OS thread handle 0x7f98f81b5700, query id 284931572 192.168.10.16 devadmin Sending data
SELECT * FROM t_voucher_pool WHERE `iStatus`=1 AND `iBindStatus`=1 AND `iCrowdID`=7 AND `sTypeCode`='LUCKYDRAW' LIMIT 1 FOR UPDATE
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10453 page no 156 n bits 792 index `idx_crowdid_typecode` of table `crowd_db`.`t_voucher_pool` trx id 264943793 lock_mode X waiting
Record lock, heap no 320 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
0: len 4; hex 00000007; asc ;;
1: len 9; hex 4c55434b5944524157; asc LUCKYDRAW;;
2: len 1; hex 01; asc ;;
3: len 4; hex 00000191; asc ;;
*** (2) TRANSACTION:
TRANSACTION 264941874, ACTIVE 374 sec updating or deleting, thread declared inside InnoDB 4999
mysql tables in use 1, locked 1
9 lock struct(s), heap size 2936, 7 row lock(s), undo log entries 2
MySQL thread id 6608580, OS thread handle 0x7f975a02b700, query id 284931884 192.168.10.16 devadmin updating
update t_voucher_pool set iBindStatus=2 where iAutoID=10401
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 10453 page no 156 n bits 792 index `idx_crowdid_typecode` of table `crowd_db`.`t_voucher_pool` trx id 264941874 lock_mode X
Record lock, heap no 320 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
0: len 4; hex 00000007; asc ;;
1: len 9; hex 4c55434b5944524157; asc LUCKYDRAW;;
2: len 1; hex 01; asc ;;
3: len 4; hex 00000191; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10453 page no 156 n bits 792 index `idx_crowdid_typecode` of table `crowd_db`.`t_voucher_pool` trx id 264941874 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 320 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
0: len 4; hex 00000007; asc ;;
1: len 9; hex 4c55434b5944524157; asc LUCKYDRAW;;
2: len 1; hex 01; asc ;;
3: len 4; hex 00000191; asc ;;
*** WE ROLL BACK TRANSACTION (1)
答案 0 :(得分:2)
假设默认隔离级别(可重复读取)。
第1节
如果没有满足WHERE
子句的索引,MySQL将使用主键。如果不指定顺序,默认情况下MySQL将按升序通过主键:
SELECT * FROM t_vouchers WHERE type='TYPE1' AND bindStatus=1 LIMIT 1 FOR UPDATE
上面的语句从主键的顶部开始,并锁定每条记录,直到它到达与WHERE
子句匹配的第一条记录。第二个查询执行相同的操作,因此您最终会获得通过目前为止结果集的最高ID锁定的所有记录。在您的示例中,那是501
。
显然,由于这些记录已被锁定,因此同一事务不需要任何额外的锁来通过更新语句更新这些记录。
第2节
SELECT * FROM t_vouchers WHERE type='TYPE1' AND bindStatus=1 LIMIT 1 FOR UPDATE
上面的语句从主键的顶部开始,并锁定每条记录,直到它到达与WHERE
子句匹配的第一条记录。当它试图锁定第一条记录时,它不能,因为会话1已将其锁定。因此,会话2等待(阻止)。
<强>结果强>
没有死锁。一次只能有一个会话锁定第一条记录。
您是否有可能同时运行200多个这样的实例?请参阅LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK
。
使用SHOW ENGINE INNODB STATUS
查看有关最新死锁的详细信息。你的问题可能在其他地方。
<强>更新强>
考虑到您的更新信息,使用辅助索引,死锁在某种程度上取决于您的数据。您应该能够通过从二级索引中删除iBindStatus来避免死锁,这将避免插入意图间隙锁定提升。