mysql 通过不同索引更新相同记录会导致死锁吗

背景

假设有一张用户表。其中id是主键列,cn_name+is_deleted上面有二级索引cn_del_index, en_name+is_deleted上面有二级索引en_del_index.

create table user(
id bigint(20) not null primary key comment '主键',
cn_name varchar(100) not null comment '中文名称',
en_name varchar(100) not null comment '英文名称',
is_deleted int not null comment '是否删除'index cn_del_index('cn_name','is_deleted'),
index en_del_name('en_name','is_deleted')
)engine = innodb;
insert into user value(1, 'lixia', 'jame', 0); 
...模拟插入很多条记录

应用程序并发执行如下两条语句:

update user set is_deleted=1 where cn_name='lixia'
update user set is_deleted=1 where cn_name='jame'

问题:这种情况下数据库会产生死锁吗?

回答1: 最坏情况下只会产生锁等待超时,不会死锁,原因是两条sql语句之间由于更新同一条记录,只需要等待另一个语句执行完即可,不会循环依赖资源,所以不会产生死锁

回答2: 并发情况下有产生死锁的可能性。

那么哪个回答是正确的呢?

技术知识

mysql数据加锁原理

对于mysql而言,我们这里只是拿innodb存储引擎举例,
一. 当执行update user set is_deleted=1 where cn_name='lixia' 这样一行update语句时,它首先需要获取以下的锁,第一个是二级索引cn_del_index上面的锁,第二个是获取主键的锁id=1,这样他就找到了对应要锁的记录了,注意这里只是第一阶段要获取的锁,由于把找到的记录的is_deleted字段更新成1后,为了保持二级索引和数据记录的数据一致性,还需要获取使用了is_deleted字段的其他二级索引的锁,比如这个例子中的en_del_index的锁(对应主键id=1).

二. 当执行update user set is_deleted=1 where cn_name='jame' 这样一行update语句时,它首先需要获取以下的锁,第一个是二级索引en_del_index上面的锁,第二个是获取主键的锁id=1,这样他就找到了对应要锁的记录了,注意这里只是第一阶段要获取的锁,由于把找到的记录的is_deleted字段更新成1后,为了保持二级索引和数据记录的数据一致性,还需要获取使用了is_deleted字段的其他二级索引的锁,比如这个例子中的cn_del_index的锁(对应主键id=1).

总结

到这里我们问题的答案已经和明显了,前面这两条sql更新记录的时候获取锁的完全可能形成互相等待的局面,比如如下:
1.第一个sql先获取了cn_del_index上面的锁,随后获取了id=1的主键上的锁,即将想要获取en_del_index上面的锁
2.第二个sql获取了en_del_index上面的锁,即将想要获取id=1主键上的锁
这种情况下两个sql互相等待对方持有的锁,死锁条件形成。

如何解决

首先我们需要意识到对于mysql最常用的read_commit隔离级别下,即使我们没有主动使用begin/end transaction开启事务,单sql语句本身也可能会导致死锁,所以应用程序需要意识到可能会产生这种死锁异常,产生死锁之后最常见的处理就是简单的重试即可.
其次,我们可以尽量减少要锁定的记录数量(比如分批执行)
来减少死锁发生的概率。
最后,我们可以限定在每次更新记录时都使用相同的二级索引,这样加锁顺序一样自然就没有死锁的问题了,最多只可能是锁等待超时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值