【问题标题】:Solution for Insert Intention Locks in MySQLMySQL中插入意向锁的解决方案
【发布时间】:2017-12-10 12:31:42
【问题描述】:

我有一个非常简单的表格:

CREATE TABLE `d` (
    `id` int(11) DEFAULT NULL,
    UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

没有记录:

select * from d;
Empty set (0,01 sec)

然后我尝试在不同的会话中打开两个事务:

第 1 场会议:

begin;
Query OK, 0 rows affected (0,00 sec)

select * from d where id = 100 for update;
Empty set (0,00 sec)

第 2 场会议:

begin;
Query OK, 0 rows affected (0,00 sec)

select * from d where id = 700 for update;
Empty set (0,00 sec)

现在我尝试在 会话 #2 中插入新记录并且会话“冻结”:

insert into d values (700);

当我尝试在 Session #1 中做同样的事情(使用另一个 id 字段)时,它会崩溃:

insert into d values (100); --> ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction in Session #1
insert into d values (700); --> Query OK, 1 row affected (4,08 sec) in Session #2

如何解决死锁? InnoDB 状态为:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-07-06 15:59:25 0x70000350d000
*** (1) TRANSACTION:
TRANSACTION 43567, ACTIVE 15 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 4, OS thread handle 123145358217216, query id 89 localhost root update
insert into d values (700)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 126 page no 4 n bits 72 index id of table `trx`.`d` trx id 43567 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 43568, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 123145357938688, query id 90 localhost root update
insert into d values (100)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 126 page no 4 n bits 72 index id of table `trx`.`d` trx id 43568 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;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 126 page no 4 n bits 72 index id of table `trx`.`d` trx id 43568 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)

【问题讨论】:

  • 你想解决什么问题 :) ?这是想知道为什么mysql正在做它正在做的事情吗?还是有您想要解决的具体问题?
  • 我尝试解决我的应用程序中的死锁(当然表 d 不是真实的)。为了更清楚起见,将此添加到问题中。

标签: mysql concurrency transactions locking


【解决方案1】:

这个死锁错误是 MySQL InnoDB 引擎中的一个 bug,已经 12 年没有修复。 (Bug #25847:https://bugs.mysql.com/bug.php?id=25847,解决方法:How do I lock on an InnoDB row that doesn't exist yet?)它与唯一密钥无关。运行这个查询也会导致同样的死锁错误。

Session #1: CREATE TABLE t (id int) ENGINE=InnoDB;
Session #1: SET AUTOCOMMIT = 0;
Session #1: SELECT id FROM t WHERE id = 1 FOR UPDATE;
Session #2: SET AUTOCOMMIT = 0;
Session #2: SELECT id FROM t WHERE id = 2 FOR UPDATE;
Session #1: INSERT INTO t (id) VALUES (1); -- Hang
Session #2: INSERT INTO t (id) VALUES (2); -- Session #1: OK, Session #2: Deadlock found when trying to get lock; try restarting transaction

InnoDB 状态相同:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-10-24 00:25:31 0x1638
*** (1) TRANSACTION:
TRANSACTION 1287, ACTIVE 62 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 7, OS thread handle 9444, query id 143 localhost ::1 root update
INSERT INTO t (id) VALUES (1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 72 index GEN_CLUST_INDEX of table `test`.`t` trx id 1287 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 1288, ACTIVE 19 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 9, OS thread handle 5688, query id 145 localhost ::1 root update
INSERT INTO t (id) VALUES (2)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 23 page no 3 n bits 72 index GEN_CLUST_INDEX of table `test`.`t` trx id 1288 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;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 23 page no 3 n bits 72 index GEN_CLUST_INDEX of table `test`.`t` trx id 1288 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)

【讨论】:

    【解决方案2】:

    请注意,从 MySQL 8.0.1 开始, 性能模式暴露了 innodb 数据锁。

    https://dev.mysql.com/doc/refman/8.0/en/data-locks-table.html

    https://dev.mysql.com/doc/refman/8.0/en/data-lock-waits-table.html

    在此示例中,仅在会话 1 中的第一次选择之后,锁是:

    mysql> select * from performance_schema.data_locks \G
    *************************** 1. row ***************************                                                                                                              
                   ENGINE: INNODB                                                                                                                                               
           ENGINE_LOCK_ID: 1808:76                                                                                                                                              
    ENGINE_TRANSACTION_ID: 1808                                                                                                                                                 
                THREAD_ID: 35                                                                                                                                                   
                 EVENT_ID: 13081                                                                                                                                                
            OBJECT_SCHEMA: test                                                                                                                                                 
              OBJECT_NAME: d                                                                                                                                                    
           PARTITION_NAME: NULL                                                                                                                                                 
        SUBPARTITION_NAME: NULL
               INDEX_NAME: NULL
    OBJECT_INSTANCE_BEGIN: 139756088373592
                LOCK_TYPE: TABLE
                LOCK_MODE: IX
              LOCK_STATUS: GRANTED
                LOCK_DATA: NULL
    *************************** 2. row ***************************
                   ENGINE: INNODB
           ENGINE_LOCK_ID: 1808:2:5:1
    ENGINE_TRANSACTION_ID: 1808
                THREAD_ID: 35
                 EVENT_ID: 13111
            OBJECT_SCHEMA: test
              OBJECT_NAME: d
           PARTITION_NAME: NULL
        SUBPARTITION_NAME: NULL
               INDEX_NAME: id
    OBJECT_INSTANCE_BEGIN: 139756088370552
                LOCK_TYPE: RECORD
                LOCK_MODE: X
              LOCK_STATUS: GRANTED
                LOCK_DATA: supremum pseudo-record <--- HERE
    2 rows in set (0.00 sec)
    

    这不是解决死锁本身的方法,但是了解所占用的锁对于理解问题大有帮助。

    这里,SELECT FOR UPDATE 都锁定了“suprenum”记录,因为 id 100 和 700 大于表中最大的 ID(它是空的)。

    一旦出现更多记录(例如 ID = 500),两个查询应同时执行,因为 ID 中的不同间隙将被锁定。

    【讨论】:

    • 谢谢。但现在我在 MySQL 5.7 上测试了它
    【解决方案3】:

    我怀疑死锁的发生是因为 InnoDB 在处理“差距”方面很保守。请注意,100 和 700 都位于未开发土地的同一模糊区域。 InnoDB 不能(或至少不能)处理“100”和“700”不同的事实。 InnoDB 想要标记单个行,但表中已经没有具有这些 id 的行。

    事务 2 可能会超时(请参阅 innodb_lock_wait_timeout)。当您第二次戳事务 1 时,#2 仍然想要锁定,InnoDB 放弃并放弃了。

    底线:存在死锁。当它们发生时,请从BEGIN 开始。您发现了另一个发生不必要死锁的晦涩案例。

    此外,我怀疑修复此案例的代码会减慢大多数其他案例的速度,并导致一系列错误,需要多个版本才能发现和修复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-27
      • 2023-03-25
      • 2012-03-25
      • 1970-01-01
      相关资源
      最近更新 更多