暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

数据库死锁:通过二级索引更新数据

徘徊笔记 2021-04-25
2231

来源:https://blog.csdn.net/zheng0518/article/details/54695605


多线程在更新数据库时报死锁问题


死锁信息


*** (1) TRANSACTION:

TRANSACTION 29285454235, ACTIVE 0.001 sec fetching rows

mysql tables in use 3, locked 3

LOCK WAIT 6 lock struct(s), heap size 1184, 4 row lock(s)

MySQL thread id 6641616, OS thread handle 0x2b165c4b1700, query id 28190427937 10.103.180.86 test_ebs Searching rows for update

UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4580605

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 132 page no 37122 n bits 352 index `PRIMARY` of table `testdriver`.`test` trx id 29285454235 lock_mode X locks rec but not gap waiting

Record lock, heap no 179 PHYSICAL RECORD: n_fields 8; compact format; info bits 0

*** (2) TRANSACTION:

TRANSACTION 29285454237, ACTIVE 0.001 sec fetching rows, thread declared inside InnoDB 4980

mysql tables in use 3, locked 3

5 lock struct(s), heap size 1184, 3 row lock(s)

MySQL thread id 6639213, OS thread handle 0x2b1694cc2700, query id 28190427939 10.103.180.113 test_ebs Searching rows for update

UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4212859

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 132 page no 37122 n bits 352 index `PRIMARY` of table `testdriver`.`test` trx id 29285454237 lock_mode X locks rec but not gap

Record lock, heap no 179 PHYSICAL RECORD: n_fields 8; compact format;




表结构:

CREATE TABLE `test` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,

`test_id` bigint(20) DEFAULT NULL,

`group_id` bigint(20) DEFAULT NULL COMMENT ‘Id,对应test_group.id’,

`gmt_created` datetime DEFAULT NULL COMMENT ‘创建时间’,

`gmt_modified` datetime DEFAULT NULL COMMENT ‘修改时间’,

`is_deleted` tinyint(4) DEFAULT ‘0’ COMMENT ‘删除。’,

PRIMARY KEY (`id`),

KEY `idx_testid` (`test_id`),

KEY `idx_groupid` (`group_id`)

) ENGINE=InnoDB AUTO_INCREMENT=7429111 ;


SQL执行计划:

mysql>explain UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 4212859


所以第一个事务先根据group_id索引,已经锁住primary id,然后再根据test_id索引,锁定primary id;

第二个事务先根据test_id索引,已经锁住primary id,然后再根据group_id索引,去锁primary id;

所以这样并发更新就可能出现死索引。


MySQL官方也已经确认了此bug:https://bugs.mysql.com/bug.php?id=77209


解决方法有两种:


第一、添加test_id+group_id的组合索引,这样就可以避免掉index merge;


第二、将优化器的index merge优化关闭;


建议选择第一种方法来避免此问题的发生。



update时,如果where条件里面涉及多个字段,区分度都比较高且字段都分别建了索引的话,mysql会多个索引各走一遍,然后结果取个交集;

单条记录更新不会引发问题;多条记录并发更新时,如果索引行数有重叠,因加锁顺序可能不同,互相等待可能会导致死锁,为什么加锁顺序会不同呢?我们的sql中where条件的顺序是一定的,那么加锁顺序也应该一定,为什么会有加锁顺序不同情况。情况是这样的,因为我们使用的是两个单值索引,where条件中是复合条件,那么mysql会使用index merge进行优化,优化过程是mysql会先用索引1进行扫表,在用索引2进行扫表,然后求交集形成一个合并索引。这个使用索引扫表的过程和我们本身的sql使用索引的顺序可能存在互斥,所以造成了死锁。

更多问题说明及解决方案请参见https://bugs.mysql.com/bug.php?id=77209


另外一个mysql死锁的场景


在事务中用for循环更新一张表,这张表中有主键和二级索引,更新就是以二级索引为条件,这时候,因为for循环里面执行的循序不一定,所以有可能导致死锁


文章转载自徘徊笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论