面试官:怎么解决MySQL死锁问题?

在这里插入图片描述

mysql中的行锁

在分析死锁之前,我们先来回忆一下MySQL中的行锁。MySQL有如下三种类型的行锁,本节只简单介绍一下,想深入了解看我的其他文章

Record Lock:对单个记录加锁
在这里插入图片描述
Gap Lock:锁住记录前面的间隙,不允许插入记录
在这里插入图片描述

Next-key Lock:同时锁住数据和数据前面的间隙,即数据和数据前面的间隙都不允许插入记录
在这里插入图片描述
Insertion Intention Gap Lock(插入意向锁)

简单的insert会在insert的行对应的索引记录上加一个排它锁,这是一个record lock,并没有gap,所以并不会阻塞其他session在gap间隙里插入记录。

不过在insert操作之前,还会加一种锁,官方文档称它为insertion intention gap lock,也就是意向的gap锁。这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock。

如果使用 Gap Lock 的话,插入的并发性将比较低

如何排查死锁?

发生死锁的条件

  1. 互斥,共享资源X和Y只能被一个线程占用
  2. 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X
  3. 不可抢占,其他线程不能强行抢占线程T1占用的资源
  4. 循环等待,线程T1等待线程T2占用的资源,线程T2等待线程T1占有的资源,就是循环等待

我们先造一个死锁的例子,给大家演示一下死锁的基本排查思路

CREATE TABLE `order_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `status` tinyint(3) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='订单表';

insert into order_info (id, status) values (1, 0), (2, 0);
时间 客户端A 客户端B
T1 begin;
update order_info set status = 1 where id = 1;
T2 begin;
update order_info set status = 2 where id = 2;
T3 update order_info set status = 1 where id = 2;
T4 update order_info set status = 2 where id = 1;
死锁回滚
T1 commit;

发生死锁时,mysql会选择一个开销最小的事务进行会滚,这样另一个事务就能正常执行

执行如下语句获取最近一次的死锁记录

show engine innodb status 

请添加图片描述
从死锁日志我们可以分析出如下有用信息

事务1死锁时正在执行如下语句

update order_info set status = 1 where id = 2;

事务2死锁时正在执行如下语句

update order_info set status = 2 where id = 1;

并且事务1等待的锁正好是事务2获取的锁,并且肯定事务2等待的锁已经被事务1获取了。

此时我们就需要去看业务代码,是如何产生死锁的,只要破坏死锁产生的条件即可。

例如想避免这个例子中的死锁,我们只需要破坏循环等待的条件,按序更新资源即可(即更新多个资源时,都是按照id从小到大更新)

生产环境死锁了

聊一个我们之前生产环境遇到的死锁问题,以便加深大家的印象(当然实际情况没那么容易排查,因为一个事务中执行的sql比较多,所以能迅速找出问题还是比较考验功底的)

有如下2张表,插入一条账户记录

CREATE TABLE `balance_account` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户编号',
  `member_code` varchar(50) NOT NULL COMMENT '用户编号',
  `balance` bigint(20) NOT NULL COMMENT '账户余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='账户表';

CREATE TABLE `balance_charger` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `member_code` varchar(50) NOT NULL COMMENT '用户编号',
  `charger_num` varchar(50) NOT NULL COMMENT '流水单据号',
  `status` tinyint(3) NOT NULL COMMENT '流水状态,0初始状态,1完成',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=latin1 COMMENT='账户流水表';

insert into balance_account(id, member_code, balance) value (1, 'a', 0);

当用户账户的钱发生变化的时候,我们需要按序操作这2张表

// 插入流水
insert balance_charger;

// 更新账户
update balance_account;

// 更新流水
update balance_charger;
时间 客户端A(Tab A) 客户端B(Tab B)
T1 begin;
insert into balance_charger (member_code, charger_num, `status`) values (‘a’, ‘v1’, 0);
update balance_account set balance = balance + 100 where id = 1;
T2 begin;
insert into balance_charger (member_code, charger_num, `status`) values (‘a’, ‘v2’, 0);
update balance_account set balance = balance + 100 where id = 1;
T3 update balance_charger set status = 1 where charger_num = ‘v1’;
T4 死锁回滚
T5 commit;

那这个死锁是如何产生的呢?
在这里插入图片描述
当update语句的where条件没有使用索引时,就会全表扫描,对全表的记录加上Next-key Lock,相当于把整个表锁住了!

如何解决呢?

很简单,给where条件中的列加索引即可

ALTER TABLE balance_charger ADD INDEX idx_charger_num(`charger_num`) 

参考博客

[1]https://mp.weixin.qq.com/s/B_slzAZLp-y8G0haZwIbTg
[2]https://bbs.huaweicloud.com/blogs/300169
插入意向锁
[3]https://www.cnblogs.com/better-farther-world2099/articles/14722850.html
[4]https://juejin.cn/post/6844903666856493064

猜你喜欢

转载自blog.csdn.net/zzti_erlie/article/details/123443999
今日推荐