我正在使用 InnoDB 运行 MySql v8.0。我有一个简单的交易,其中有一个
SELECT
(不是 SELECT ... FOR UPDATE
),后跟一个 INSERT
。当许多数据库插入同时发生时,我经常遇到死锁。所有插入都使用相同的代码,因此应该具有相同的获取锁顺序。表的主索引基于 id
,它是自动递增的。但死锁似乎是由于二级索引上的同时锁定而发生的。 SHOW ENGINE INNODB STATUS
的输出是:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-11-11 05:43:03 70378481278848
*** (1) TRANSACTION:
TRANSACTION 75372951333, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 10225859, OS thread handle 70369406140288, query id 1436757204 172.22.120.99 _99952768SM1GL7w update
insert into shoes
( account_id
, premium
, name
, state
)
values
( 22496527
, 0
, 'boots'
, 'active'
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 1087 page no 634440 n bits 432 index index_shoes_on_account_id_and_premium_and_name of table `staging`.`shoes` trx id 75372951333 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;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1087 page no 634440 n bits 432 index index_shoes_on_account_id_and_premium_and_name of table `staging`.`shoes` trx id 75372951333 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 75372951336, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 10225858, OS thread handle 70373717872512, query id 1436757206 172.22.120.99 _99952768SM1GL7w update
insert into shoes
( account_id
, premium
, name
, state
)
values
( 22496526
, 0
, 'boots'
, 'active'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1087 page no 634440 n bits 432 index index_shoes_on_account_id_and_premium_and_name of table `staging`.`shoes` trx id 75372951336 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 1087 page no 634440 n bits 432 index index_shoes_on_account_id_and_premium_and_name of table `staging`.`shoes` trx id 75372951336 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)
为什么两个事务都对具有相同空间id的记录持有排他锁?由于插入的行不同并且具有不同的索引,为什么这会成为问题呢?这是否与这两个事务都会导致索引大于当前最大索引这一事实有关?即,两者都会导致至上值的更新?
因为两个事务都插入不同的行并将创建不同的索引记录,所以我希望两个事务都能继续进行而不会导致死锁。
编辑:
SHOW CREATE TABLE shoes
的输出是:
CREATE TABLE `shoes` (
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`premium` tinyint(1) DEFAULT '0',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`state` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `index_shoes_on_account_id_and_premium_and_name` (`account_id`,`premium`,`name`),
KEY `shoes_account_id_name_idx2` (`account_id`,`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
也许这就是正在发生的事情。
两个
INSERTs
正在将新行插入到BTree处理中
UNIQUE KEY (`account_id`,`premium`,`name`),
INSERT
和下面的 account_id
正在做锁定,以避免其他人潜入该 BTree 中的同一位置。 其他 INSERT
落在同一地点并弃踢。
那么,你会问:“为什么不能解决呢?” 我认为答案是,对于如此罕见的事情来说,解决这个问题的努力太高了。
这应该是一个可行的修复方法:每次
INSERT
之后,检查是否有失败。 如果发生死锁,则重播中止的事务。 到那时,另一个查询将完成并释放锁。
这是许多微妙的僵局之一,导致了在必要时始终检查和重放的规则。