【发布时间】:2015-10-21 04:43:51
【问题描述】:
在下面运行这段代码时,我遇到了死锁。
代码的目的是在 Title 表中插入一个新的 Title,最终结果是如果没有其他标题已经设置了 defaultTitle 位,我需要设置 defaultTitle 位。
Title Table 是 Product Table 的外键(因此 ProductId 上存在非唯一索引)。 Title 表如下所示:
CREATE TABLE `Title` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ProductId` int(11) DEFAULT NULL,
`Title` varchar(100) NOT NULL,
`DefaultBit` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`ID`),
KEY `fk_product_Title` (`ProductId`),
CONSTRAINT `title_fk_1` FOREIGN KEY (`ProductId`) REFERENCES `product` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在此示例事务中,我想为同一产品在 Title 表中添加 2 个新标题。如果我同时在 2 个不同的会话中运行此代码,它总是会死锁。
我想要的结果是没有死锁,第一个事务将 DefaultBit 设置为 1,第二个事务将 DefaultBit 设置为 0。
START TRANSACTION;
SET @prodId = 4;
SET @insTitleDefault = (SELECT IF(COUNT(PT.ID) > 0, 0, 1) as defaultOrNot
FROM ProductTitle PT
WHERE PT.ProductId = @prodId);
SELECT SLEEP(2); - Added for testing to pinpoint the deadlock.
INSERT INTO Title
(`ProductId`,`Title`,`DefaultBit`)
VALUES
(@prodId ,"Some title text",@insTitleDefault);
SET @newTitleId = LAST_INSERT_ID();
SELECT * FROM Title WHERE ProductId = @prodId;
-- COMMIT; -- commenting out the commit for testing purposes, Rollback instead
ROLLBACK;
我已尝试添加 FOR UPDATE
SET @insTitleDefault = (SELECT IF(COUNT(PT.ID) > 0, 0, 1) as defaultOrNot
FROM ProductTitle PT
WHERE PT.ProductId = @prodId FOR UPDATE);
但这也不起作用,仍然死锁。
我还尝试按照Deadlock using SELECT ... FOR UPDATE in MySQL 中的答案以不同的隔离模式运行整个事务
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
这不会导致死锁,但也不会保留正确的 DefaultBit,因为它将两个事务值都设置为 1(并且每个产品应该只有 1 个默认值)。
到目前为止我的分析:
我已经运行了 show ENGINE INNODB status;每次死锁后,获取以下相关信息:
LATEST DETECTED DEADLOCK
------------------------
2015-07-30 10:05:18 3808
*** (1) TRANSACTION:
TRANSACTION 227273, ACTIVE 6 sec inserting
mysql tables in use 2, locked 2
LOCK WAIT 6 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1
MySQL thread id 93, OS thread handle 0x433c, query id 3098292 localhost 127.0.0.1 user update
INSERT INTO Title(`ProductId`,`Title`,`DefaultBit`)
VALUES(@pId,"Some title text",@insTitleDefault)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 380 page no 132 n bits 752 index `fk_product_Title` of table `globalhq`.`Title` trx id 227273 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 227274, ACTIVE 4 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 2, locked 2
6 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1
MySQL thread id 95, OS thread handle 0x3808, query id 3098300 localhost 127.0.0.1 user update
INSERT INTO Title(`ProductId`,`Title`,`DefaultBit`)
VALUES(@pId,"Some title text",@insTitleDefault)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 380 page no 132 n bits 752 index `fk_product_Title` of table `globalhq`.`Title` trx id 227274 lock mode S
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 380 page no 132 n bits 752 index `fk_product_Title` of table `globalhq`.`Title` trx id 227274 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
我知道这是正在发生的事情
- TX1 在 select 语句上加了一个 S 锁
- TX2 也在 select 语句上加了一个 S 锁
- TX1 将 IX 锁放在 Insert 语句上,但从第 2 点被阻止
- TX2 将 IX 锁放在 Insert 语句上,但从点 2 和 3 被阻止,因此在 TX 2 上出现死锁。
当我在 select 语句中添加 FOR UPDATE 时:
- TX1 在 select 语句上设置了 IX 锁
- TX2 还在 select 语句上设置了 IX 锁
- TX1 将 X 锁放在插入语句上,但从第 2 点被阻止
- TX2 将 X 锁放在插入语句上,但从点 2 和 3 被阻止,因此在 TX 2 上出现死锁。
我不确定正确的前进方式,因为我尝试过的每种方法都使程序陷入僵局。任何建议都会很棒。
【问题讨论】:
标签: mysql multithreading locking deadlock isolation-level