Mysql中一个奇怪的死锁

时间:2015-03-05 15:33:02

标签: mysql innodb

我有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)

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来避免死锁,这将避免插入意图间隙锁定提升。