【问题标题】:Mysql deadlock on "SELECT ... FOR UPDATE" and insert“SELECT ... FOR UPDATE”上的Mysql死锁并插入
【发布时间】: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)

我知道这是正在发生的事情

  1. TX1 在 select 语句上加了一个 S 锁
  2. TX2 也在 select 语句上加了一个 S 锁
  3. TX1 将 IX 锁放在 Insert 语句上,但从第 2 点被阻止
  4. TX2 将 IX 锁放在 Insert 语句上,但从点 2 和 3 被阻止,因此在 TX 2 上出现死锁。

当我在 select 语句中添加 FOR UPDATE 时:

  1. TX1 在 select 语句上设置了 IX 锁
  2. TX2 还在 select 语句上设置了 IX 锁
  3. TX1 将 X 锁放在插入语句上,但从第 2 点被阻止
  4. TX2 将 X 锁放在插入语句上,但从点 2 和 3 被阻止,因此在 TX 2 上出现死锁。

我不确定正确的前进方式,因为我尝试过的每种方法都使程序陷入僵局。任何建议都会很棒。

【问题讨论】:

    标签: mysql multithreading locking deadlock isolation-level


    【解决方案1】:

    我建议您在“PT.ProductId”列上添加索引。
    我有同样的问题并像上面一样解决。
    原理是当索引存在时,'select for update' 将锁定索引,其他事务的'select for update' 或'insert' 将被阻塞,直到第一个事务提交。所以不会发生死锁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-24
      • 2014-03-10
      • 1970-01-01
      • 2010-10-31
      • 1970-01-01
      • 2013-10-19
      相关资源
      最近更新 更多