【问题标题】:question about InnoDB deadlock in MySQL?关于 MySQL 中的 InnoDB 死锁的问题?
【发布时间】:2010-03-10 19:01:26
【问题描述】:

我在 MySQL InnoDB 引擎中发现了这种有趣的问题,谁能解释为什么引擎总是声称它是死锁。

首先,我创建了一个单行单列的表:

   CREATE TABLE `SeqNum` (`current_seq_num` bigint(30) NOT NULL default '0',
                           PRIMARY KEY  (`current_seq_num`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
   Query OK, 0 rows affected (0.03 sec)

   mysql> insert into SeqNum values (5);
   Query OK, 1 row affected (0.00 sec)

现在,我有两个 MySQL 连接器线程,在 thread1:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select `current_seq_num` into @curr_seq FROM SeqNum FOR UPDATE;
    Query OK, 1 row affected (0.00 sec)

现在,在 thread2 中,我做了完全相同的事情:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select `current_seq_num` into @curr_seq FROM SeqNum FOR UPDATE;

在默认的innodb_lock_wait_timeout之前,thread2只是等待thread1释放对表的排他锁,这是正常的。

但是,在thread1中,如果我输入以下更新查询:

     mysql> update SeqNum set `current_seq_num` = 8;
     ERROR 1213 (40001): Deadlock found when trying to get lock; 
     try restarting transaction

现在,thread2 完成了 select 查询,因为 thread1 退出了。

另外,在thread1中,如果我输入带有where子句的更新查询,可以很好的执行:

     mysql> update SeqNum set `current_seq_num` = 8 where `current_seq_num` =5
     Query OK, 1 row affected (0.00 sec)

谁能解释一下?

【问题讨论】:

  • 我认为区别在于死锁和疑似死锁。只是猜测,因为他们使用相同的锁,并且需要一段时间,这是一个死锁的情况。
  • 我无法复制您的事件序列(我相信我昨天可以)。现在,从线程 1 执行的任何更新都没有出现死锁。请您用整个序列(包括插入 SeqNum)以及有关版本和隔离模式的信息来更新问题?
  • Martin:我用详细的序列事件更新了问题。我只是再次尝试以确保它始终相同。
  • WilliamLou:我完全按照你的顺序,但我没有陷入僵局。也许您正在运行一个有错误的版本。你在运行哪个版本?我已经尝试过 5.1.44-community (Windows) 和 5.1.37-1ubuntu5.1。

标签: mysql deadlock innodb


【解决方案1】:

为什么你甚至想要执行这条 SQL 而不是

REPLACE INTO SeqNum VALUES (NULL); 
SELECT last_insert_id();

【讨论】:

    【解决方案2】:

    “SELECT ... FOR UPDATE”在 SeqNum 表上放置一个 INTENTION EXCLUSIVE (IX) 锁,并在所有符合 SELECT 条件的行上放置一个 EXCLUSIVE (X) 锁。

    可以使用 Innodb 锁监视器查看锁的状态。这是通过创建一个特殊命名的表来实现的:

    create table innodb_lock_monitor( i int not null ) engine = innodb;
    

    只要发出以下命令,就会显示锁的状态:

    show engine innodb status \G
    

    当第一个线程执行“Select ... for update”时,会放置以下锁(我在表中有一行值为5):

    MySQL thread id 42, query id 338 localhost root
    TABLE LOCK table `test`.`SeqNum` trx id 0 1284 lock mode IX
    RECORD LOCKS space id 0 page no 51 n bits 72 index `PRIMARY` of table `test`.`SeqNum` trx id 0 1284 lock_mode X
    Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
     0: len 8; hex 73757072656d756d; asc supremum;;
    
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
     0: len 8; hex 8000000000000005; asc         ;; 1: len 6; hex 000000000503; asc       ;; 2: len 7; hex 800000002d0110; asc     -  ;;
    

    这是表上的 IX 锁和两个 X 锁 - 一个在唯一行之后的间隙(上)上,另一个在实际数据行上。

    在第二个线程执行“select ... for update”时,会添加以下锁:

    TABLE LOCK table `test`.`SeqNum` trx id 0 1285 lock mode IX
    RECORD LOCKS space id 0 page no 51 n bits 72 index `PRIMARY` of table `test`.`SeqNum` trx id 0 1285 lock_mode X waiting
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
     0: len 8; hex 8000000000000005; asc         ;; 1: len 6; hex 000000000503; asc       ;; 2: len 7; hex 800000002d0110; asc     -  ;;
    

    这是表上的一个新 IX 锁,加上唯一数据行上的一个“X 等待”锁。

    原始线程可以对整个表或唯一的数据行运行更新,而不会出现死锁。

    这是运行“5.1.37-1ubuntu5.1”,具有 REPEATABLE-READ。

    见:

    MySQL Manual - 13.6.8.1. InnoDB Lock Modes

    【讨论】:

    • 马丁:恐怕我不能同意你的看法。请注意,如果添加 where 子句,则可以执行更新查询。我的观点是:如果设置 IX 锁的会话无法更新行,那么设置 IX 锁有什么意义?
    • 你是对的。就在我认为我理解某些东西时... :-) 我应该删除这个答案吗?我需要更详细地了解正在发生的事情。您是否尝试过使用 Innodb 监视器查看此内容?
    • WilliamLou:我在再次阅读手册后更新了答案。好问题。
    • 我现在唯一不明白的部分是为什么第二个“SELECT ... FOR UPDATE”阻塞:如果 IX 锁兼容,为什么第二个被阻塞但仍然被授予?
    • Martin:是的,这次你的解释很有道理,但我还是有点困惑。让我们命名两个线程A和B,我的理解是:A已经有一个IX锁,B想要一个IX锁。这两个锁发生冲突,所以 B 等待 A 释放它的 IX 锁。现在,A 想要一个需要 X 锁的更新(有或没有 where),为什么它不能这样做呢?对于表 SeqNum,整个表也是它唯一的行。
    猜你喜欢
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多