mysql - Mysql中的一个奇怪的死锁

标签 mysql innodb

我有 2 种不同类型的 1,000 张代金券。我想将它们提供给我们网站上的一些特殊用户。因此,每个用户将获得每种类型的 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 时工作正常。但是当并发级别提高时,我发现一些请求会失败,它们是由死锁引起的。如果 session 2step 1 上挂起,而 session 1 试图执行step 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 节

如果没有索引来满足 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 子句匹配的第一条记录。当它试图锁定第一条记录时,它不能,因为 Session 1 已经锁定了它。因此, session 2 等待(阻塞)。

结果

没有死锁。一次只有一个 session 可以锁定第一条记录。

您是否有可能同时运行 200 多个这样的实例?参见 LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK .

使用SHOW ENGINE INNODB STATUS查看最新死锁的详细信息。您的问题可能出在其他地方。

更新

考虑到您更新的信息,使用二级索引,死锁在某种程度上取决于您的数据。您应该能够通过从二级索引中删除 iBindStatus 来避免死锁,这将避免插入意图间隙锁提升。

关于mysql - Mysql中的一个奇怪的死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28881660/

相关文章:

php - 选择....用于更新和 if 语句与 php

php - CSV 文件创建期间内存不足

mysql - PDO 和行数

MySql InnoDB - 在表的 "Not Null"列中插入

mysql - XAMPP MySQL InnoDB 错误

mysql - 确定要删除的 MySQL 行数以达到目标数据库大小

php - Phalcon 迁移系统中的 auto_increment

c++ - 设计模式会破坏抽象吗?

python - 如何从 scrapy 运行中获取统计信息?

mysql - 将设置为使用 MyISAM 的程序转换为 INNODB