mysql中的隔离性原理详解

 更新时间:2023年08月09日 08:29:59   作者:meihaoshy  
这篇文章主要介绍了mysql隔离性的原理,多版本并发控制(MVCC)是一种用来解决 读-写冲突 的无锁并发控制,为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照,需要的朋友可以参考下

数据库并发的三种场景

  • 读-读 :不存在任何问题,也不需要并发控制
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写 :有线程安全问题,可能会存在更新丢失问题

在这三种场景中 读-读几乎没有任何问题 所以我们不需要并发控制

写-写并发只需要加锁控制即可

所以说我们今天重点讨论下读-写并发

MVCC

基本介绍

多版本并发控制( MVCC )是一种用来解决 读-写冲突 的无锁并发控制

为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

在我们理解MVCC之前 我们需要知道三个前提知识

  • 3个记录隐藏字段
  • undo 日志
  • Read View

我们下面就分别先介绍下这三个隐藏字段

三个前提知识介绍

三个隐藏字段

  • DB_TRX_ID :6 byte,最近修改( 修改/插入 )事务ID,记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR : 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就行,这些数据一般在 undo log 中)
  • DB_ROW_ID : 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以DB_ROW_ID 产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

假设我们现在创建并且插入了一条数据 代码和显示如下

mysql> create table if not exists student(
name varchar(11) not null,
age int not null
);
mysql> insert into student (name, age) values ('张三', 28);
Query OK, 1 row affected (0.05 sec)
mysql> select * from student;
+--------+-----+
| name | age |
+--------+-----+
| 张三 | 28 |
+--------+-----+
1 row in set (0.00 sec)

实际上在Linux隐藏字段的效果就是

在这里插入图片描述

对于上图做出一定解释

  • 假设插入的事务ID是9 那么TRX_ID字段实际上就是9
  • 因为这是我们插入的第一个数据 所以说隐式主键就是1
  • 因为这是第一个数据 没有更前面的数据了 所以说回滚指针指向的就是空
  • 其实还有其他的隐藏字段 比如说flag等 上面没有标识出

undo log日志

mySQL 将来是以服务进程的方式,在内存中运行。我们之前所讲的所有机制:索引,事务,隔离性,日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。

所以,我们这里理解undo log,简单理解成,就是 MySQL 中的一段内存缓冲区,用来保存日志数据的就行。

read view 快照

关于快照读的知识下面模拟MVCC场景的时候会讲

Read View就是事务进行 快照读 操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)

Read View 在 MySQL 源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件,用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。

下面是 ReadView 结构,但为了减少同学们负担,我们简化一下

class ReadView {
	// 省略...
private:
	/** 高水位:大于等于这个ID的事务均不可见*/
	trx_id_t m_low_limit_id;
	/** 低水位:小于这个ID的事务均可见 */
	trx_id_t m_up_limit_id;
	/** 创建该 Read View 的事务ID*/
	trx_id_t m_creator_trx_id;
	/** 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
	trx_id_t m_low_limit_no;
	/** 标记视图是否被关闭*/
	bool m_closed;
	// 省略...
};
m_ids; //一张列表,用来维护Read View生成时刻,系统正活跃的事务ID
up_limit_id; //记录m_ids列表中事务ID最小的ID
low_limit_id; //ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
creator_trx_id //创建该ReadView的事务ID

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的DB_TRX_ID 。

那么,我们现在手里面有的东西就有,当前快照读的 ReadView 和 版本链中的某一个记录的DB_TRX_ID 。

那么 现在我们的问题就是 当前快照读,应不应该读到当前版本记录。一张图,解决所有问题!

在这里插入图片描述

如果查到不应该看到当前版本,接下来就是遍历下一个版本,直到符合条件,即可以看到。上面的readview 是当你进行select的时候,会自动形成。

看到这里有的同学可能会产生这样一个疑问 如何遍历下个版本呢?

我们之前说过 undo log其实就是一个缓冲区 并且里面有着回滚指针连接着的各种数据 (实际上就是单链表连接的各种数据)

再次总结下

  • 我们第一次开启事务的时候会生成一个read view的结构体
  • 该结构体中会记录活跃的最小事务id和比最大事务id还要大一的事务id
  • 当我们第一次select读的时候会形成一个快照
  • 如果说当前版本的TRX_ID小于最小的id 那么我们就可以见到
  • 如果当前版本的TRX_ID大于等于最大ID我们就不能见到
  • 如果说在最小和最大区间里面 并且该TRX_ID不是活跃ID(已提交) 则我们可以看到
  • 如果说在最小和最大区间里面 并且TRX_ID还是活跃ID(未提交) 则我们不能看到

转化成现实中的例子

现在的我们能够看到我们出生之前所有人写的作品 但是我们不能看到还未出生的人写的作品

如果说写书的人跟我们同一个时代 我们就要判断这本书有没有发表 (是否提交) 如果提交了我们就能看见 如果没有提交 我们就不能看见

模拟MVCC场景

MVCC场景中有增删改查 下面我们分别进行讨论

我们插入的时候只需要形成一条新的undo log版本链 将回滚指针指向前面的数据 如果需要回滚直接通过回滚指针找到需要覆盖的数据进行覆盖即可

我们前面说过了 mysql中还有一个隐藏的falg字段 因此 如果需要删除的话 只需要将flag标志位设置即可

这是最麻烦的一个环节 我们使用一个例子来说明MVCC中的改

现在一个表中有如下的记录

在这里插入图片描述

现在有一个事务ID为10的事务 要修改表中的name张三为李四

  • 因为要修改 所以我们肯定要先给记录上锁
  • 修改之前 我们要将改之前的数据拷贝一份要undo log当中 假设地址为0x11223344
  • 之后我们修改原始数据中name为李四 并且将回滚指向0x11223344这个地址
  • 事务10commit提交 释放锁

过程图如下

在这里插入图片描述

如果还有事务要修改新的数据就参考上面的步骤即可

于是乎我们就形成了一条基于链表记录的历史版本链 undo log里面的一个个历史版本就称为快照

现在我们明白了

  • 所谓的回滚其实就是拿历史版本链中的某条数据覆盖当前数据

首先我们要理解两个概念 当前读和快照读

  • 当前读:读取最新的记录,就是当前读。增删改,都叫做当前读,select也有可能当前读,比如select lock in share mode(共享锁), select for update
  • 快照读:读取历史版本。快照读不会被加锁。

多个事务同时增删改的时候是当前读 需要加锁 在串行化的隔离级别下 select也是当前读 需要加锁

如果select是快照读 那么和增删改的当前读不冲突 所以说并行效率高 事务的隔离级别决定了select是当期读还是快照读 具体的判断方法可以参考前面read view部分的知识

RR和RC的区别

RR级别测试

演示一 两边开启事务 右边先进行快照读 左边插入数据之后commit 右边再进行快照读和当前读

在这里插入图片描述

在这里插入图片描述

我们可以发现的是 当右边使用快照读的时候不管左边有没有commit 读取到的数据是一样的

而使用当前读的时候 我们可以发现读取的数据就是最新的数据了 光靠这个一个试验我们看不出来什么 接下来我们看演示二

演示二: 左右两边同时开启一个事务 左边先插入数据之后提交 右边在左边提交之后进行快照读

在这里插入图片描述

我们发现 这个时候右边的快照读 读取了最新的数据

对比这两次试验加上之前的read view部分学习我们不难做出以下的推断

在RR级别下 第一次select快照读的时候会生成一个read view快照 之后的读取就按照这个快照进行

而实际上在RC级别中 每一次的select快照都都会生成一个最新的read view快照

所以说RR和RC最本质的区别就是 RR只会生成依次read view快照 而RC快照读几次就会生成几次快照

四种隔离级别的不同处理方式

读–未提交

直接当前读 不加锁

串行化

当前读 加锁

读 提交

在RC级别中 每次的select读取都是快照读 每次都会生成一个最新的read view快照

可重复读

在RR级别中 每次select读取都是快照读 并且都会遵循第一次select读取时生成的read view快照

总结

在这里插入图片描述

到此这篇关于mysql隔离性的原理的文章就介绍到这了,更多相关mysql隔离性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL定时任务不能正常执行的原因分析及解决方法

    MySQL定时任务不能正常执行的原因分析及解决方法

    大家好,本篇文章主要讲的是MySQL定时任务不能正常执行的原因分析及解决方法,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • MySQL HandlerSocket插件安装配置教程

    MySQL HandlerSocket插件安装配置教程

    这篇文章主要介绍了MySQL HandlerSocket插件安装配置,包括PHP HandlerSocket插件的安装配置,需要的朋友可以参考下
    2014-04-04
  • mysql 开发技巧之JOIN 更新和数据查重/去重

    mysql 开发技巧之JOIN 更新和数据查重/去重

    这篇文章主要介绍了mysql 开发技巧之JOIN 更新和数据查重/去重的相关资料,需要的朋友可以参考下
    2016-09-09
  • mysql配置连接参数设置及性能优化

    mysql配置连接参数设置及性能优化

    这篇文章主要介绍了mysql配置连接参数设置及性能优化,主要介绍配置信息的说明和性能优化,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-05-05
  • MySQL 数据类型选择原则

    MySQL 数据类型选择原则

    MySQL支持大量的数据类型,选择正确的类型对性能十分关键。本篇介绍了MySQL 的数据类型选择原则,可以根据这些基本的原则确定数据表字段的具体数据类型。
    2021-05-05
  • PHP中常用的几个 mysql操作

    PHP中常用的几个 mysql操作

    本篇文章是对关于php操作mysql执行数据库查询的一些常用操作进行了详细的汇总介绍,非常的细致全面,也很简单,需要的朋友参考下
    2015-04-04
  • 详解MySQL如何保证数据一致性

    详解MySQL如何保证数据一致性

    对于一个数据库而言,除了数据的持久性、不丢失之外,一致性也是非常重要的,不然这个数据是没有任何意义的,在使用MySQL时,数据不一致的情况也可能出现,所以,本文就来看看MySQL是如何保证数据一致的,需要的朋友可以参考下
    2024-01-01
  • mysql的case when字段为空,null的问题

    mysql的case when字段为空,null的问题

    这篇文章主要介绍了mysql的case when字段为空,null的问题。具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • MySQL数据库子查询语法规则详解

    MySQL数据库子查询语法规则详解

    子查询是在查询语句里面再嵌套一个查询,这是因为我们在提取数据的时候有很多不知道的数据产生了依赖关系。本文为大家总结了一下MySQL数据库子查询语法规则,感兴趣的可以了解一下
    2022-08-08
  • mysql5.6 解析JSON字符串方式(支持复杂的嵌套格式)

    mysql5.6 解析JSON字符串方式(支持复杂的嵌套格式)

    这篇文章主要介绍了mysql5.6 解析JSON字符串方式(支持复杂的嵌套格式),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07

最新评论