文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一文带你了解MySQL之锁

2023-08-16 18:48

关注

上一篇文章主要学习了事务并发执行时可能带来的各种问题,并发事务访问相同记录的情况我们大致可以划分为3种:

很明显,采用MVCC方式的话,读-写操作彼此并不冲突,性能更高,采用加锁方式的话,读-写操作彼此需要排队执行,影响性能。一般情况下我们当然愿意采用MVCC来解决读-写操作并发执行的问题,但是业务在某些特殊情况下,要求必须采用加锁的方式执行,那也是没有办法的事。

1.1 一致性读(Consistent Reads)

事务利用MVCC进行的读取操作称之为一致性读,或者一致性无锁读,有的地方也称之为快照读。所有普通的SELECT语句(plain SELECT)在READ COMMITTEDREPEATABLE READ隔离级别下都算是一致性读,比如:

SELECT * FROM t;SELECT * FROM t1 INNER JOIN t2 ON t1.col1 = t2.col2

我们需要知道的是,一致性读并不会对表中的任何记录做加锁操作,其他事务可以自由的对表中的记录做改动。

1.2 锁定读(Locking Reads)

1.2.1 共享锁和独占锁

我们前边说过,并发事务的读-读情况并不会引起什么问题,不过对于写-写读-写写-读这些情况可能会引起一些问题,需要使用MVCC或者加锁的方式来解决它们。在使用加锁的方式解决问题时,由于既要允许读-读情况不受影响,又要使写-写读-写写-读情况中的操作相互阻塞,所以MySQL给锁分了个类:

假如事务T1首先获取了一条记录的S锁之后,事务T2接着也要访问这条记录:

如果事务T1首先获取了一条记录的X锁之后,那么不管事务T2接着想获取该记录的S锁还是X锁都会被阻塞,直到事务T1提交。

所以我们说S锁S锁是兼容的,S锁X锁是不兼容的,X锁X锁也是不兼容的,画个表表示一下就是这样:

兼容性XS
X不兼容不兼容
S不兼容兼容

1.2.2 锁定读的语句

我们前边说在采用加锁方式解决脏读、不可重复读、幻读这些问题时,读取一条记录时需要获取一下该记录的S锁,其实这是不严谨的,有时候想在读取记录时就获取记录的X锁,来禁止别的事务读写该记录,为此MySQL的提出了两种比较特殊的SELECT语句格式:

对读取的记录加S锁:

SELECT ... LOCK IN SHARE MODE;

也就是在普通的SELECT语句后边加LOCK IN SHARE MODE,如果当前事务执行了该语句,那么它会为读取到的记录加S锁,这样允许别的事务继续获取这些记录的S锁(比方说别的事务也使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录),但是不能获取这些记录的X锁(比方说使用SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。如果别的事务想要获取这些记录的X锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的S锁释放掉

对读取的记录加X锁:

SELECT ... FOR UPDATE;

也就是在普通的SELECT语句后边加FOR UPDATE,如果当前事务执行了该语句,那么它会为读取到的记录加X锁,这样既不允许别的事务获取这些记录的S锁(比方说别的事务使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录),也不允许获取这些记录的X锁(比如说使用SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。如果别的事务想要获取这些记录的S锁或者X锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的X锁释放掉

关于更多锁定读加锁细节我们稍后会详细讲解,稍安勿躁

1.3 写操作

我们平常所用到的写操作无非是DELETEUPDATEINSERT这三种

我们前边提到的都是针对记录的,也可以被称之为行级锁或者行锁,对一条记录加锁影响的也只是这条记录而已,我们就说这个锁的粒度比较细;其实一个事务也可以在表级别进行加锁,自然就被称之为表级锁或者表锁,对一个表加锁影响整个表中的记录,我们就说这个锁的粒度比较粗。给表加的锁也可以分为共享锁S锁)和独占锁X锁

上边看着有点啰嗦,为了更好的理解这个表级别的S锁X锁,我们举一个现实生活中的例子。不知道各位同学都上过大学没,我们以大学教学楼中的教室为例来分析一下加锁的情况:

上边提到的这两种锁都是针对教室而言的,不过有时候我们会有一些特殊的需求:

但是这里头有两个问题:

我们在对教学楼整体上锁(表锁)时,怎么知道教学楼中有没有教室已经被上锁(行锁)了呢?依次检查每一间教室门口有没有上锁?那这效率也太慢了吧!遍历是不可能遍历的,这辈子也不可能遍历的,于是InnoDB的提出了一种称之为意向锁(英文名:Intention Locks)的东东:

视角回到教学楼和教室上来:

之后:

小提示:
学生在教学楼门口加IS锁时,是不关心教学楼门口是否有IX锁的,维修工在教学楼门口加IX锁时,是不关心教学楼门口是否有IS锁或者其他IX锁的。IS和IX锁只是为了判断当前时间教学楼里有没有被占用的教室用的,也就是在对教学楼加S锁或者X锁时才会用到。

总结一下:ISIX锁表级锁,它们的提出仅仅为了在之后加表级别的S锁X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实IS锁IX锁是兼容的,IX锁IX锁是兼容的。我们画个表来看一下表级别的各种锁的兼容性:

兼容性XIXSIS
X不兼容不兼容不兼容不兼容
IX不兼容兼容不兼容兼容
S不兼容不兼容兼容兼容
IS不兼容兼容兼容兼容

上边说的都算是些理论知识,其实MySQL支持多种存储引擎,不同存储引擎对锁的支持也是不一样的。当然,我们重点还是讨论InnoDB存储引擎中的锁,其他的存储引擎只是稍微提一下~

3.1 其他存储引擎中的锁

对于MyISAMMEMORYMERGE这些存储引擎来说,它们只支持表级锁,而且这些引擎并不支持事务,所以使用这些存储引擎的锁一般都是针对当前会话来说的。比方说在Session 1中对一个表执行SELECT操作,就相当于为这个表加了一个表级别的S锁,如果在SELECT操作未完成时,Session 2中对这个表执行UPDATE操作,相当于要获取表的X锁,此操作会被阻塞,直到Session 1中的SELECT操作完成,释放掉表级别的S锁后,Session 2中对这个表执行UPDATE操作才能继续获取X锁,然后执行具体的更新语句

3.2 InnoDB存储引擎中的锁

InnoDB存储引擎既支持表锁,也支持行锁表锁实现简单,占用资源较少,不过粒度很粗,有时候你仅仅需要锁住几条记录,但使用表锁的话相当于为表中的所有记录都加锁,所以性能比较差。行锁粒度更细,可以实现更精准的并发控制。下边我们详细看一下

3.2.1 InnoDB中的表级锁

表级别的S锁、X锁

在对某个表执行SELECTINSERTDELETEUPDATE语句时,InnoDB存储引擎是不会为这个表添加表级别S锁或者X锁的。

另外,在对某个表执行一些诸如ALTER TABLEDROP TABLE这类的DDL语句时,其他事务对这个表并发执行诸如SELECTINSERTDELETEUPDATE的语句会发生阻塞,同理,某个事务中对某个表执行SELECTINSERTDELETEUPDATE语句时,在其他会话中对这个表执行DDL语句也会发生阻塞。这个过程其实是通过在server层使用一种称之为元数据锁(英文名:Metadata Locks,简称MDL)来实现的,一般情况下也不会使用InnoDB存储引擎自己提供的表级别的S锁X锁

小提示:
在事务简介的文章中我们说过,DDL语句执行时会隐式的提交当前会话中的事务,这主要是DDL语句的执行一般都会在若干个特殊事务中完成,在开启这些特殊事务前,需要将当前会话中的事务提交掉。

其实这个InnoDB存储引擎提供的表级S锁或者X锁是相当鸡肋,只会在一些特殊情况下,比方说崩溃恢复过程中用到。不过我们还是可以手动获取一下的,比方说在系统变量autocommit=0,innodb_table_locks = 1时,手动获取InnoDB存储引擎提供的表t的S锁或者X锁可以这么写:

不过我们尽量避免在使用InnoDB存储引擎的表上使用LOCK TABLES这样的手动锁表语句,它们并不会提供什么额外的保护,只是会降低并发能力而已。InnoDB的厉害之处还是实现了更细粒度的行锁,关于表级别的S锁X锁大家了解一下就罢了。

表级别的IS锁、IX锁

当我们在对使用InnoDB存储引擎的表的某些记录加S锁之前,那就需要先在表级别加一个IS锁,当我们在对使用InnoDB存储引擎的表的某些记录加X锁之前,那就需要先在表级别加一个IX锁IS锁IX锁的使命只是为了后续在加表级别的S锁X锁时判断表中是否有已经被加锁的记录,以避免用遍历的方式来查看表中有没有上锁的记录。更多关于IS锁IX锁的解释我们上边已经讲解了,就不赘述了。

表级别的AUTO-INC锁

在使用MySQL过程中,我们可以为表的某个列添加AUTO_INCREMENT属性,之后在插入记录时,可以不指定该列的值,系统会自动为它赋上递增的值,比方说我们有一个表:

CREATE TABLE t (    id INT NOT NULL AUTO_INCREMENT,    c VARCHAR(100),    PRIMARY KEY (id));

由于这个表的id字段声明了AUTO_INCREMENT,也就意味着在书写插入语句时不需要为其赋值,比方说这样:

INSERT INTO t(c) VALUES('aa'), ('bb');

上边的插入语句并没有为id列显式赋值,所以系统会自动为它赋上递增的值,效果就是这样:

mysql> SELECT * FROM t;+----+------+| id | c    |+----+------+|  1 | aa   ||  2 | bb   |+----+------+2 rows in set (0.00 sec)

系统实现这种自动给AUTO_INCREMENT修饰的列递增赋值的原理主要是两个:

3.2.2 InnoDB中的行级锁

很遗憾的通知大家一个不好的消息,上边讲的都是铺垫,本章真正的重点才刚刚开始

行锁,也称为记录锁,顾名思义就是在记录上加的锁。不过InnoDB把一个行锁玩出了各种花样,也就是把行锁分成了各种类型。换句话说即使对同一条记录加行锁,如果类型不同,起到的功效也是不同的。为了学习的顺利发展,我们还是先将之前学习的MVCC时用到的表抄一遍:

mysql> CREATE TABLE hero(number INT PRIMARY KEY,name VARCHAR(4),country VARCHAR(2));Query OK, 0 rows affected (0.03 sec)

我们主要是想用这个表存储王者的英雄,然后向这个表里插入几条记录:

mysql> INSERT INTO hero VALUES    (1, 'l刘备', '蜀国'),    (3, 'z诸葛亮', '蜀国'),    (8, 'c曹操', '蜀国'),    (15, 'x项羽', '西楚'),    (20, 's孙权', '吴国');Query OK, 5 rows affected (0.01 sec)Records: 5  Duplicates: 0  Warnings: 0

小提示:
为啥要在’刘备’、‘曹操’、‘孙权’前边加上’l’、‘c’、‘s’这几个字母呀?这个主要是因为我们采用utf8mb4字符集,该字符集并没有对应的按照汉语拼音进行排序的比较规则,也就是说’刘备’、‘曹操’、'孙权’这几个字符串的排序并不是按照它们汉语拼音进行排序的,所以在汉字前边加上了汉字对应的拼音的第一个字母,这样在排序时就是按照汉语拼音进行排序。

另外,我们故意把各条记录number列的值搞得很分散,后边会用到,稍安勿躁哈~我们把hero表中的聚簇索引的示意图画一下:

在这里插入图片描述
当然,我们把B+树的索引结构做了一个超级简化,只把索引中的记录给拿了出来,我们这里只是想强调聚簇索引中的记录是按照主键大小排序的,并且省略掉了聚簇索引中的隐藏列,大家心里明白就好(不理解索引结构的同学可以去前边的文章中查看)。

现在准备工作做完了,下边我们来看看都有哪些常用的行锁类型。

我们前边说对一条记录加锁的本质就是在内存中创建一个锁结构与之关联,那么是不是一个事务对多条记录加锁,就要创建多个锁结构呢?比方说事务T1要执行下边这个语句:

# 事务T1SELECT * FROM hero LOCK IN SHARE MODE;

很显然这条语句需要为hero表中的所有记录进行加锁,那是不是需要为每条记录都生成一个锁结构呢?其实理论上创建多个锁结构没问题,反而更容易理解,但是谁知道你在一个事务里想对多少记录加锁呢,如果一个事务要获取10000条记录的锁,要生成10000个这样的结构也太亏了吧!InnoDB的决定在对不同记录加锁时,如果符合下边这些条件:

那么这些记录的锁就可以被放到一个锁结构中。当然,这么空口白牙的说有点儿抽象,我们还是画个图来看看InnoDB存储引擎中的锁结构具体长啥样吧:

在这里插入图片描述
我们看看这个结构里边的各种信息都是干嘛的:

可能上边的描述大家觉得还是有些抽象,我们还是举个例子说明一下。比方说现在有两个事务T1T2想对hero表中的记录进行加锁,hero表中记录比较少,假设这些记录都存储在所在的表空间号为67,页号为3的页面上,那么如果:

综上所述,事务T1number值为5的记录加锁生成的锁结构就如下图所示:

在这里插入图片描述

至此今天的学习就到此结束了,愿您成为坚不可摧的自己~~~

You can’t connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future.You have to trust in something - your gut, destiny, life, karma, whatever. This approach has never let me down, and it has made all the difference in my life

如果我的内容对你有帮助,请 点赞评论收藏,创作不易,大家的支持就是我坚持下去的动力!

本文章参考:小孩子《MySQL是怎样运行的》
在这里插入图片描述

来源地址:https://blog.csdn.net/liang921119/article/details/131060086

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯