Mysql 百问系列:死锁是怎么发生的

本文主要从共享锁(S锁)和独占锁(X锁)出发,详细说明两种锁的加锁机制,以及死锁如何产生。mysql

问题:

  1. 什么是共享锁,什么是独占锁?
  2. 死锁怎么发生的?
  3. 发生死锁了,Mysql 如何处理的?

共享锁和独占锁

上一篇文章中咱们已经讲解了共享锁和独占锁的基本概念,我这边再详细将一下。sql

  • 共享锁 Shared Locks (S 锁)  从名字就能够看出来它容许共享,共享的意思是当你给某个事物加上锁后,其余人还能够为它加上(仅限)共享锁。
  • 独占锁 Exclusive Locks (X 锁), 若是想要修改某个事物,必须先加上X锁,可是若是该事物已经错在了S锁或者X锁就必须等已经存在的锁释放了才能再加上去。
S锁 X锁
S锁 兼容 不兼容
X锁 不兼容 不兼容

了解了S锁和X锁,那么咱们来看看什么是死锁吧。
让咱们来复现下死锁的过程,以便更好理解。建议能够根据本身理解来复现死锁过程,并分析缘由加深理解。否则变成:
眼睛: 我会了。
脑子: 不,你不会。数据库

准备工做

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  `addresss` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT student VALUES (1,'张一',12,'ssss'),(2,'张二',12,'ssss'),(3,'张三',12,'ssss'),(4,'张四',12,'ssss'),(5,'张五',12,'ssss');
复制代码

准备工做完成,创建表,并插入5条数据。并发

模拟死锁第一步
新建一个数据库链接并执行:高并发

###事务A
begin;
select * from student where id in (1,2) lock in share mode;
复制代码

步骤一 事务A 获得了id 为1,2的S锁。
再新建一个数据库链接并执行:ui

###事务B
begin;
select * from student where id in (1,3) lock in share mode;
复制代码

步骤二 这时事务B 获得了id为1,3的S锁
而后咱们在事务B 中尝试获取数据id = 2 的X锁spa

事务B
update student set age  =100 where id = 2
复制代码

步骤三, 因为id为2 被事务A加了S锁,因此事务B 被阻塞。
最后咱们在事务A中尝试获取数据id = 3 的X锁rest

事务A
update student set age  =100 where id = 3

#执行结果:1213 - Deadlock found when trying to get lock; try restarting transaction, Time: 0.005000s
复制代码

步骤四,因为id为3 被事务B加了S锁,因此事务A被阻塞。 固然其实执行的时候被跳出死锁警告。事务A回滚撤销了。code

到此,咱们模拟了整个死锁过程,因为B 为了得到X锁须要等A执行完成,而A后续反而为了得到X锁须要等B执行完成。因此形成了死锁。事务

死锁的缘由

从上面的例子中咱们能够看到死锁的缘由是相互寻求各自手上的资源(即锁)致使的。
可是其中有个重要的特性须要说明就是二阶段锁协议。 
二阶段锁协议很简单:

  1. 须要查询或者修改时才加锁。
  2. 事务提交的时候才释放锁。

上面例子中步骤三,B之因此会被阻塞,是由于事务A虽然读取数据结束了,可是事务还没结束,因此致使读取是加的锁没有释放。
也是因为二阶段锁协议的缘由,因此咱们应该尽量避免长事务。由于`事务执行时间越长,加锁的时间越长,发生死锁的几率越大```

死锁如何处理

mysql 能够设置两种处理方式:

  1. 第一种就是上面例子锁展现的,发生死锁时,自动回滚其中一个事务,解除死锁。 能够经过innodb_deadlock_detect 参数设置为on 开启。默认状况下就是on 状态。
  2. 设置超时时间,也就是发生死锁时先等待一段时间后,再退出。能够设置innodb_lock_wait_timeout 设置超时时间。

通常状况下,咱们会用第一种方式。

留个问题

现实场景中有哪些容易发生死锁的场景呢?其中最多见的一个例子是减库存了。
扣减库存咱们通常有如下步骤

  1. 查看现有库存是否充足
  2. 库存充足则减去库存。 不足则返回扣除失败。

假设流程的sql语句以下

# 查询剩余库存
select last_number from stock where  id = 1000 lock in share mode;

#库存足够 --减去库存
update stock set last_number = last_number - 1 where id = 1000;
复制代码

那么问题是:

  1. 当并发比较高的时候,上面这个业务功能会不会发生死锁,分析下发生死锁的缘由?
  2. 为何要用lock in share mode 加锁,不加锁行不行? 高并发下,若是不加锁会发生什么问题?
  3. 怎么修改才能不发生死锁,又能避免其余问题?

问题答案下篇文章回答。有收获,请关注下吧。

相关文章
相关标签/搜索