请选择 进入手机版 | 继续访问电脑版

[MySql] MySQL变乱 的隔离性是怎样 实现的

[复制链接]
查看57 | 回复8 | 2021-9-12 21:59:15 | 显示全部楼层 |阅读模式

update,delete,insert 都会主动 给涉及到的数据加上排他锁,select 语句默认不会加任何锁

那什么环境 下会对读操作加锁呢?

  • select … lock in share mode,对读取的记录加S锁
  • select … for update ,对读取的记录加X锁
  • 在事件 中读取记录,对读取的记录加S锁
  • 事件 隔离级别在 SERIALIZABLE 下,对读取的记录加S锁

InnoDB中有如下三种锁

  • Record Lock:对单个记录加锁
  • Gap Lock:间隙锁,锁住记录前面的间隙,不答应 插入记录
  • Next-key Lock:同时锁住数据和数据前面的间隙,即数据和数据前面的间隙都不答应 插入记录

写个Demo演示一下

  1. CREATE TABLE `girl` (
  2. `id` int(11) NOT NULL,
  3. `name` varchar(255),
  4. `age` int(11),
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
  1. insert into girl values
  2. (1, '西施', 20),
  3. (5, '王昭君', 23),
  4. (8, '貂蝉', 25),
  5. (10, '杨玉环', 26),
  6. (12, '陈圆圆', 20);
复制代码

Record Lock

对单个记录加锁

如把id值为8的数据加一个Record Lock,表示 图如下

在这里插入图片形貌

Record Lock也是有S锁和X锁之分的,兼容性和之前形貌 的一样。

SQL实行 加什么样的锁受很多条件的制约,比如事件 的隔离级别,实行 时使用 的索引(如,聚集索引,非聚集索引等),因此就不详细 分析了,举几个简单的例子。

  1. -- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主键进行等值查询
  2. -- 对id=8的记录加S型Record Lock
  3. select * from girl where id = 8 lock in share mode;
  4. -- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主键进行等值查询
  5. -- 对id=8的记录加X型Record Lock
  6. select * from girl where id = 8 for update;
复制代码

Gap Lock

锁住记录前面的间隙,不答应 插入记录

MySQL在可重复读隔离级别下可以通过MVCC和加锁来办理 幻读标题

当前读:加锁
快照读:MVCC

但是该怎样 加锁呢?由于 第一次实行 读取操作的时间 ,这些幻影记录并不存在,我们没有办法加Record Lock,此时可以通过加Gap Lock办理 ,即对间隙加锁。

在这里插入图片形貌

如一个事件 对id=8的记录加间隙锁,则意味着不答应 别的事件 在id=8的记录前面的间隙插入新记录,即id值在(5, 8)这个区间内的记录是不答应 立即 插入的。直到加间隙锁的事件 提交后,id值在(5, 8)这个区间中的记录才可以被提交

我们来看如下一个SQL的加锁过程

  1. -- REPEATABLE READ 利用主键进行等值查询
  2. -- 但是主键值并不存在
  3. -- 对id=8的聚集索引记录加Gap Lock
  4. SELECT * FROM girl WHERE id = 7 LOCK IN SHARE MODE;
复制代码

由于id=7的记录不存在,为了克制 幻读征象 (避免在同一事件 下实行 雷同 的语句得到的效果 集中有id=7的记录),以是 在当前事件 提交前我们要防备 别的事件 插入id=7的记录,此时在id=8的记录上加一个Gap Lock即可,即不答应 别的事件 插入id值在(5, 8)这个区间的新记录

在这里插入图片形貌

给大家提一个标题 ,Gap Lock只能锁定记录前面的间隙,那么末了 一条记录后面的间隙该怎么锁定?

实在 mysql数据是存在页中的,每个页有2个伪记录

  • Infimum记录,表示该页面中最小的记录
  • upremum记录,表示该页面中最大的记录

为了防止别的 事件 插入id值在(12, +∞)这个区间的记录,我们可以给id=12记录地点 页面的Supremum记录加上一个gap锁,此时就可以制止 其他事件 插入id值在(12, +∞)这个区间的新记录

Next-key Lock

同时锁住数据和数据前面的间隙,即数据和数据前面的间隙都不答应 插入记录
以是 你可以如许 明确 Next-key Lock=Record Lock+Gap Lock

在这里插入图片形貌

  1. -- REPEATABLE READ 利用主键进行范围查询
  2. -- 对id=8的聚集索引记录加S型Record Lock
  3. -- 对id>8的所有聚集索引记录加S型Next-key Lock(包括Supremum伪记录)
  4. SELECT * FROM girl WHERE id >= 8 LOCK IN SHARE MODE;
复制代码

由于 要办理 幻读的标题 ,以是 必要 禁别的事件 插入id>=8的记录,以是

  • 对id=8的聚集索引记录加S型Record Lock
  • 对id>8的全部 聚集索引记录加S型Next-key Lock(包括Supremum伪记录)

表级锁

表锁也有S锁和X锁之分

在对某个表实行 select,insert,update,delete语句时,innodb存储引擎是不会为这个表添加表级别的S锁或者X锁。

在对表实行 一些诸如ALTER TABLE,DROP TABLE这类的DDL语句时,会对这个表加X锁,因此其他事件 对这个表实行 诸如SELECT INSERT UPDATE DELETE的语句会发生壅闭

在体系 变量autocommit=0,innodb_table_locks = 1时,手动获取InnoDB存储引擎提供的表t的S锁或者X锁,可以这么写

对表t加表级别的S锁

  1. lock tables t read
复制代码

对表t加表级别的X锁

  1. lock tables t write
复制代码

假如 一个事件 给表加了S锁,那么

  • 别的事件 可以继续获得该表的S锁
  • 别的事件 可以继续获得表中某些记录的S锁
  • 别的事件 不可以继续获得该表的X锁
  • 别的事件 不可以继续获得表中某些记录的X锁

假如 一个事件 给表加了X锁,那么

  • 别的事件 不可以继续获得该表的S锁
  • 别的事件 不可以继续获得表中某些记录的S锁
  • 别的事件 不可以继续获得该表的X锁
  • 别的事件 不可以继续获得表中某些记录的X锁

以是 修改线上的表时肯定 要警惕 ,由于 会使大量事件 壅闭 ,现在 有很多成熟的修改线上表的方法,不再赘述

隔离级别

读未提交:每次读取最新的记录,没有做特殊 处理
串行化:事件 串行实行 ,不会产生并发

以是 我们重点关注读已提交可重复读的隔离实现!

这两种隔离级别是通过MVCC(多版本并发控制)来实现的,本质就是MySQL通过undolog存储了多个版本的汗青 数据,根据规则读取某一汗青 版本的数据,如许 就可以在无锁的环境 下实现读写并行,进步 数据库性能

那么undolog是怎样 存储修改前的记录?

对于使用 InnoDB存储引擎的表来说,聚集索引记录中都包含下面2个必要的潜伏 列

trx_id:一个事件 每次对某条聚集索引记录举行 改动时,都会把该事件 的事件 id赋值给trx_id潜伏 列

roll_pointer:每次对某条聚集索引记录举行 改动时,都会把旧的版本写入undo日记 中。这个潜伏 列就相当 于一个指针,通过他找到该记录修改前的信息

假如 一个记录的name从貂蝉被依次改为王昭君,西施,会有如下的记录,多个记录构成了一个版本链

在这里插入图片形貌

为了判定 版本链中哪个版本对当前事件 是可见的,MySQL计划 出了ReadView的概念。4个紧张 的内容如下

  • m_ids:在天生 ReadView时,当前体系 中活跃的事件 id列表
  • min_trx_id:在天生 ReadView时,当前体系 中活跃的最小的事件 id,也就是m_ids中的最小值
  • max_trx_id:在天生 ReadView时,体系 应该分配给下一个事件 的事件 id值
  • creator_trx_id:天生 该ReadView的事件 的事件 id

当对表中的记录举行 改动时,实行 insert,delete,update这些语句时,才会为事件 分配唯一的事件 id,否则一个事件 的事件 id值默以为 0。

max_trx_id并不是m_ids中的最大值,事件 id是递增分配的。比如现在 有事件 id为1,2,3这三个事件 ,之后事件 id为3的事件 提交了,当有一个新的事件 天生 ReadView时,m_ids的值就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4

请添加图片形貌

实行 过程如下:

  • 假如 被访问版本的trx_id=creator_id,意味着当前事件 在访问它本身 修改过的记录,以是 该版本可以被当前事件 访问
  • 假如 被访问版本的trx_id
  • 被访问版本的trx_id>=max_trx_id,表明天生 该版本的事件 在当前事件 天生 ReadView后才开启,该版本不可以被当前事件 访问
  • 被访问版本的trx_id是否在m_ids列表中
  • 4.1 是,创建ReadView时,该版本还是活跃的,该版本不可以被访问。顺着版本链找下一个版本的数据,继续实行 上面的步骤判定 可见性,假如 末了 一个版本还不可见,意味着记录对当前事件 完全不可见
  • 4.2 否,创建ReadView时,天生 该版本的事件 已经被提交,该版本可以被访问

好了,我们知道了版本可见性的获取规则,那么是怎么实现读已提交和可重复读的呢?

实在 很简单,就是天生 ReadView的机遇 不同

举个例子,先建立如下表

  1. CREATE TABLE `girl` (
  2. `id` int(11) NOT NULL,
  3. `name` varchar(255),
  4. `age` int(11),
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

Read Committed

Read Committed(读已提交),每次读取数据前都天生 一个ReadView

在这里插入图片形貌

下面是3个事件 实行 的过程,一行代表一个时间点

在这里插入图片形貌

先分析一下5这个时间点select的实行 过程

  • 体系 中有两个事件 id分别为100,200的事件 正在实行
  • 实行 select语句时天生 一个ReadView,mids=[100,200],min_trx_id=100,max_trx_id=201,creator_trx_id=0(select这个事件 没有实行 更改操作,事件 id默以为 0)
  • 最新版本的name列为西施,该版本trx_id值为100,在mids列表中,不符合可见性要求,根据roll_pointer跳到下一个版本
  • 下一个版本的name列王昭君,该版本的trx_id值为100,也在mids列表内,因此也不符合要求,继续跳到下一个版本
  • 下一个版本的name列为貂蝉,该版本的trx_id值为10,小于min_trx_id,因此末了 返回的name值为貂蝉

在这里插入图片形貌

再分析一下8这个时间点select的实行 过程

  • 体系 中有一个事件 id为200的事件 正在实行 (事件 id为100的事件 已经提交)
  • 实行 select语句时天生 一个ReadView,mids=[200],min_trx_id=200,max_trx_id=201,creator_trx_id=0
  • 最新版本的name列为杨玉环,该版本trx_id值为200,在mids列表中,不符合可见性要求,根据roll_pointer跳到下一个版本
  • 下一个版本的name列为西施,该版本的trx_id值为100,小于min_trx_id,因此末了 返回的name值为西施

当事件 id为200的事件 提交时,查询得到的name列为杨玉环。

Repeatable Read

Repeatable Read(可重复读),在第一次读取数据时天生 一个ReadView

在这里插入图片形貌

可重复读由于 只在第一次读取数据的时间 天生 ReadView,以是 每次读到的是雷同 的版本,即name值不停 为貂蝉,详细 的过程上面已经演示了两遍了,我这里就不重复演示了,信赖 你肯定 会本身 分析了。

参考博客

[1]https://souche.yuque.com/bggh1p/8961260/gyzlaf
[2]https://zhuanlan.zhihu.com/p/35477890

到此这篇关于MySQL事件 的隔离性是怎样 实现的的文章就先容 到这了,更多相干 MySQL事件 的隔离性内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的相干 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
目次

并发场景

迩来 做了一些分布式事件 的项目,对事件 的隔离性有了更深的认识 ,后续写文章聊分布式事件 。本日 就复盘一下单机事件 的隔离性是怎样 实现的?

隔离的本质就是控制并发,假如 SQL语句就是串行实行 的。那么数据库的四大特性中就不会有隔离性这个概念了,也就不会有脏读,不可重复读,幻读等各种标题 了

对数据库的各种并发操作,只有如下四种,写写,读读,读写和写读

写-写

事件 A更新一条记录的时间 ,事件 B能同时更新同一条记录吗?

答案肯定是不能的,不然就会造成脏写标题 ,那怎样 避免脏写呢?答案就是加锁

读-读

MySQL读操作默认环境 下不会加锁,以是 可以并行的读

读-写 和 写-读

基于各种场景对并发操作容忍程度不同,MySQL就搞了个隔离性的概念。你本身 根据业务场景选择隔离级别。

√ 为会发生,×为不会发生

隔离级别 脏读 不可重复读 幻读
read uncommitted(未提交读)
read committed(提交读) ×
repeatable read(可重复读) × ×
serializable (可串行化) × × ×

以是 你看,MySQL是通过锁和隔离级别对MySQL举行 并发控制的

MySQL中的锁

行级锁

InnoDB存储引擎中有如下两种范例 的行级锁

  • 共享锁(Shared Lock,简称S锁),在事件 必要 读取一条记录时,必要 先获取改记录的S锁
  • 排他锁(Exclusive Lock,简称X锁),在事件 要改动一条记录时,必要 先获取该记录的X锁

假如 事件 T1获取了一条记录的S锁之后,事件 T2也要访问这条记录。假如 事件 T2想再获取这个记录的S锁,可以成功,这种环境 称为锁兼容,假如 事件 T2想再获取这个记录的X锁,那么此操作会被壅闭 ,直到事件 T1提交之后将S锁开释 掉

假如 事件 T1获取了一条记录的X锁之后,那么不管事件 T2接着想获取该记录的S锁还是X锁都会被壅闭 ,直到事件 1提交,这种环境 称为锁不兼容。

多个事件 可以同时读取记录,即共享锁之间不互斥,但共享锁会壅闭 排他锁。排他锁之间互斥

S锁和X锁之间的兼容关系如下

兼容性 X锁 S锁
X锁 互斥 互斥
S锁 互斥 兼容

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 喜欢吃芒果干俺 | 2021-9-18 21:07:23 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的源码论坛他的站点都是商业源码,还是免费下载的那种!特别好用。访问地址:http://www.mxswl.com 猫先森网络
回复

使用道具 举报

avatar 想做的都做了吗 | 2021-9-26 09:01:05 | 显示全部楼层
我默默的回帖,从不声张!
回复

使用道具 举报

avatar 123457376 | 2021-10-1 21:58:19 | 显示全部楼层
谢谢admin楼主的分享!
回复

使用道具 举报

avatar 仙翁童子子os | 2021-10-7 23:43:40 | 显示全部楼层
记得吃药!
回复

使用道具 举报

楼上的说的很好!
回复

使用道具 举报

看了这么多帖子,第一次看到这么高质量内容!
回复

使用道具 举报

今天是个特别的日子,值得纪念!
回复

使用道具 举报

admin楼主就是我的榜样哦
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则