mysql中的锁

发布时间:2023-12-08 14:30

mysql中的锁

      • 环境
      • 表锁
        • S锁
        • X锁
      • 行级锁和意向锁
        • S行锁
        • X行锁
        • Gap锁和Next-key锁
      • 死锁

环境

此次实验所使用的mysql版本为mysql8:

mysql中的锁_第1张图片

因为我们可以在performance_schema.data_locks中看到锁的情况。

引擎是innodb。事务隔离级别是RR。

表锁

mysql中的锁可以按照锁的粒度分,表锁就是其中的一种(另一种是行锁)。

虽然锁一整张表在实际操作中是不明智的,但我们还是探究一下。

准备的表:

CREATE TABLE `test`.`test_innodb_lock` (
  `id` int NOT NULL,
  `name` varchar(255) NOT NULL,
  `score` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_score` (`score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

我们有主键索引id以及二级索引score

然后插入一些数据:

INSERT INTO `test`.`test_innodb_lock`(`id`, `name`, `score`) VALUES (1, 'Jack', 100);
INSERT INTO `test`.`test_innodb_lock`(`id`, `name`, `score`) VALUES (2, 'Rose', 60);
INSERT INTO `test`.`test_innodb_lock`(`id`, `name`, `score`) VALUES (3, 'Ocean', 77);

为了看到performance_schema.data_locks中锁的情况,我们需要设置:

set autocommit=0;

S锁

首先看看表级别的读锁:

mysql中的锁_第2张图片
mysql中的锁_第3张图片

读锁可以被多次获取(session1和session2同时获取到了读锁)。

mysql中的锁_第4张图片

我们可以看到in_use的数量增加了。

然后我们查看锁的情况:

SELECT
  ENGINE,
  OBJECT_SCHEMA,
  OBJECT_NAME,
  INDEX_NAME,
  LOCK_TYPE,
  LOCK_MODE,
  LOCK_STATUS,
  LOCK_DATA
FROM performance_schema.data_locks;

mysql中的锁_第5张图片

可以看到一个S锁(Share Lock)。

在读锁锁整张表的情况下:

mysql中的锁_第6张图片

三个session都能够读。

但是:

mysql中的锁_第7张图片

锁表的session1和session2报错,旁观者session3阻塞。

mysql中的锁_第8张图片
我们的确可以看到一个阻塞的语句。

mysql中的锁_第9张图片

session1和session2回滚并解锁表。

X锁

重新做实验。

mysql中的锁_第10张图片
在session1获取表级别的写锁之后,session2无法再次获取。我们取消session2的获取操作。

mysql中的锁_第11张图片

现在我们看到了一把X锁(Exclusive Lock)。

mysql中的锁_第12张图片
持有锁的session1能读能写,其他session则不行。

我们可以看一下mysql官网的总结:

mysql中的锁_第13张图片

行级锁和意向锁

我们不会主动去锁表,我们常用的是行级别的锁。

行级别的锁有很多。我们看二级索引score,它有几个值:60,79,100,由此可以划分区间。

小于60
60
60至79
79
79至100
大于100

如果我们要添加锁,我们可以锁一行,比如score=60这一行,也可以锁区间,比如60至79这个区间,一切都要看innodb怎么去实现了。

S行锁

mysql中的锁_第14张图片

全部开启事务。

mysql中的锁_第15张图片
我们看到了两种锁:IS表示Intention Share Lock,S表示Share Lock,REC_NOT_GAP表示Record Lock but Not a Gap Lock(行锁而非间隙锁)。所以S,REC_NOT_GAP就表示这是一个行锁,是个共享锁,不是一个间隙锁。

IS是一种表锁,如果一个session想去获取一个S锁,就必须先去获取IS锁,即使你最终可能获取S锁失败了,你也要去持有IS锁(表达自己的一个意愿)。

mysql中的锁_第16张图片
S锁是一种行级的共享锁,作用在主键上(一定要有主键,你没有人家给你生成一个)。

既然id=1的行被session1持有了行锁,我们看看session2能做些什么?

mysql中的锁_第17张图片

可以查,但是不能修改。

mysql中的锁_第18张图片

我们可以看到有人试图去获取id=1这一行X锁(写锁),显然这是session2的意图。在waiting地去获取id=1条记录的X锁时,他已经成功地获取了IX锁(Intention Exclusive Lock)。

mysql中的锁_第19张图片

当session2因为无法获取写锁而超时时:

mysql中的锁_第20张图片

IX锁还是被session2持有着,这个Intention还是保留着。

mysql中的锁_第21张图片

session2rollback

mysql中的锁_第22张图片

此时IX锁才被释放。


和表级S锁一样行级的S锁也可以被多个session获得。

mysql中的锁_第23张图片
mysql中的锁_第24张图片
mysql中的锁_第25张图片

读锁的含义其实就是当前事务进入了读的状态,当然所有事务都可以这么做。但是,如果你想改,不好意思,没有一个事务中能改,不管你持不持有读锁。只有当这一行的读锁全部释放,它才能够被修改。


X行锁

同理,我们可以看看行级的X锁。

mysql中的锁_第26张图片
和料想的一样,一个IX锁,一个X锁。

mysql中的锁_第27张图片
mysql中的锁_第28张图片
session2只能拿到IX锁,拿不到X锁。

mysql中的锁_第29张图片
当然,session2也拿不到读锁。

mysql中的锁_第30张图片

只有持有写锁的session1才能修改。

所以for update或者说X锁的含义究竟是什么?他就像是在说:我锁定了(session1),接下来要有写操作了,其他人不能进入read mode,也不能进入write mode,其他人就等我完事吧。


我们的表里有主键索引id和二级索引score,暂时我们都在使用主键索引,我们可以先看看如果查询语句不使用索引会怎样(使用二级索引score作为查询条件的话情况会更加复杂)。

mysql中的锁_第31张图片

三条记录都被锁住了。而且是我们没有接触到的一种锁,单纯的S锁(next-key lock),等下我们会介绍这个锁。

当前就认为id=1,id=2,id=3三条记录都添加了行锁就行。

mysql中的锁_第32张图片

此时session2对id=3的记录也不能更新了。

作为对比,我们看看如果使用主键索引的情况:

mysql中的锁_第33张图片

mysql中的锁_第34张图片
只锁id=1一行的话session2是可以更新id=3的数据的。


另外,我们常用的其他当前读:update和delete,mysql也是给加了写锁的。

mysql中的锁_第35张图片
mysql中的锁_第36张图片


Gap锁和Next-key锁

gap锁就是间隙锁,我们上面看到过,我们不仅能对一行加锁,还能对行与行之间的间隙加锁。

next-key锁就是锁两个东西,一个是锁行,另一个是锁行前面的那个间隙,就是这两把锁的合并。

为了更好的演示,我们修改一下数据:

mysql中的锁_第37张图片

我们为二级索引加写锁:

mysql中的锁_第38张图片
这里:

  • IX是意向锁,要拿到X必须先拿到IX锁。
  • X是next-key锁,锁住score=60对应的行和score在50至60之间的数据。
  • X,REC_NOT_GAP是行锁,锁住id=21这一行。
  • X,GAP是间隙锁,锁住score在60至70间的数据。

边界数据score=50是否锁住需要查看。

我们先看插入的情况:

mysql中的锁_第39张图片

50(包含)到60,60到76都不能插入,但是score=77可以。这里就可以看出next-key lock和gap lock之间的区别了。

mysql中的锁_第40张图片

对于update操作,51(包含)到60,60到76都不行,score改成50或者77可以。

从这里也可以看出gap lock和next-key lock作用范围也太大了。


mysql中的锁_第41张图片

如果没有使用索引,则给全表加上next-key lock。

mysql中的锁_第42张图片

这时候没有任何插入操作能够成功。

当我们使用RR的时候,需要注意gap lock和next-key lock带来的开销。

死锁

死锁就是两个事务(或者多个)互相持有并去索取锁(和java死锁一样)。

mysql中的锁_第43张图片

session1先去持有id=2这一行的S锁。

mysql中的锁_第44张图片

mysql中的锁_第45张图片

session2尝试去获取id=2这行的X锁,由于SX互斥,session2等待。

mysql中的锁_第46张图片

在session2等待的过程中,session1去获取id=2这行的X锁。这时候死锁就发生了:

mysql中的锁_第47张图片

session2已经发出请求X锁的需要,等待session1释放S锁;session1又去请求正被session2“持有”的X锁,互相盯着对方碗里的锁,就会发生死锁。(session1的S锁不能直接升级成X锁,因为它已经被session2请求了)

innodb自己会发现死锁并打破循环。

mysql中的锁_第48张图片

此时session1同时获取id=2这一行的S锁和X锁。

使用

SHOW ENGINE INNODB STATUS

我们可以看到更多细节:

session2的情况:

mysql中的锁_第49张图片

session1的情况:

mysql中的锁_第50张图片

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号