一、业务介绍及四大特点

事务是指在执行过程中要么全部成功,要么全部失败的一组命令操作。

如果引擎层支持事务,则不支持事务,但支持事务。

事务具有以下四个特征(ACID):

简单列出了4个特性以及相应的实现方法。 ACID的详细实现原理将在另一个空间公开。 朋友们,记得关注我哦!

mysql数据库基础教程_数据库mysql知识点整理_mysql数据库基础知识

2.脏读、不可重复读和幻读

当事务存在并发时,会出现以下问题。

脏读

即从其他事务中读取未提交的数据。

事务A读取了事务B未提交的数据,此时如果事务B出现错误,进行了回滚操作,那么事务A读取到的数据就是脏数据。

看来原始数据还是比较干净、纯粹的。 这时候,因为事务B改变了它,数据就变得不再纯粹了。

这时,事务A立即读取了脏数据,但是事务B良心发现了,并使用回滚将数据恢复到原来干净纯粹的样子,但是事务A却什么都不知道,最终的结果就是事务A一次读到的脏数据就称为脏读。

这种情况在转账、提现操作中经常出现

按时间顺序

转账交易

提现交易

开业

开业

查询账户余额为2000元

提现1000元,余额变为1000元

查询账户余额为1000元(出现脏读)

提现操作发生未知错误,交易回滚,余额改为2000元

转出2000元,余额改为3000元(脏读1000+2000)

提交事务

评论

按照正确的逻辑,此时账户余额应该是4000元

数据库mysql知识点整理_mysql数据库基础知识_mysql数据库基础教程

不可重复读

即一个事务前后多次读取,数据内容不一致。

事务A正在执行读操作。 由于整个事务A比较大,所以读取同一条数据需要很长时间。

比如事务A第一次读取数据的时候,读取到此时小明的年龄是20岁,事务B进行更改操作,将小明的年龄改为30岁。 此时事务A第二次读取小明的年龄,当年龄为30岁时,发现其年龄为30岁,与之前的数据不同,即数据不重复,系统无法读取重复的数据,成为不可重复读。

按时间顺序

交易A

交易B

开业

第一个查询中,小明的年龄是20岁

开业

其他操作

将小明的年龄改为30

提交事务

第二个查询中,小明的年龄是30岁

评论

按照正确的逻辑,事务A前后两次读取的数据应该是一致的

幻读

即某个事务前后多次读取,读取的数据总量不一致。

事务A在执行读操作时,需要统计两次数据总量。 上次查询数据总量后,事务B已经执行了添加新数据的操作并提交了。 此时事务A读取的总数据量与之前统计的不一样,就像幻觉一样,无缘无故多了几条数据,这就是所谓的幻读。

按时间顺序

交易A

交易B

开业

第一次查询,总数据量为100

开业

其他操作

添加100条数据

提交事务

第二次查询,总数据量为200条

评论

按照正确的逻辑,事务A前后两次读取的数据总量应该是一致的

3.事务隔离级别

事务隔离级别就是为了不同程度地解决上述问题。

隔离级别有四种,分别是

未提交的读

在此隔离级别下,所有事务都可以从其他事务中读取未提交的数据。

从其他事务中读取未提交的数据会导致脏读。 因此,在该隔离级别下,无法解决脏读、不可重复读、幻读等问题。

未提交的读可能会导致脏读,那么如何解决脏读呢? 那就是使用已提交读。

读已提交

在此隔离级别下,所有事务只能读取其他事务已提交的内容。

可以彻底解决脏读现象。 但在这种隔离级别下,会出现一个事务前后多次查询返回不同内容数据的现象,即出现不可重复读。

这是大多数数据库系统的默认隔离级别,例如SQL,但不是mysql。

提交可能会产生不可重复读的现象,我们可以使用可重复读。

可重复读取

在该隔离级别下,所有事务前后多次读取的数据内容保持不变。

即某个事务执行过程中,不允许其他事务进行操作,但允许其他事务进行添加操作,导致某个事务前后多次读取的数据总量不一致的现象,导致幻读。

这是mysql默认的事务隔离级别

重复读取仍会产生幻读。 这时候我们就可以使用序列化来解决。

序列化

在该隔离级别下,所有事务都是顺序执行的,因此它们之间不存在冲突,可以有效解决脏读、不可重复读、幻读等现象。

然而,安全性和效率不能同时实现。 序列化会大大降低数据库的性能,一般不使用该级别。

下面用表格表示可以解决的问题,x表示未解决,√表示可以解决。

隔离级别

脏读

不可重复读

幻读

未提交的读(读)

X

X

X

已提交读(读)

X

X

可重复读取(Read)

X

可序列化()

当然,上述隔离级别以及当前级别存在的问题只是一个规范,不同的数据库厂商可能有不同的实现。

比如mysql在可重复读的层面上,已经通过按键锁的方式解决了幻读的问题。

4.MVCC

为了实现上述隔离级别,mysql提出了LBCC(Lock-Based,基于锁的并发控制)和MVCC(Multi-,基于多版本的并发控制)。

LBCC中,针对读写冲突,采用记录锁、间隙锁、相邻键锁等锁来实现数据并发安全,因此读写性能不高。关于锁的分类,可以参考我的另一篇关于锁类型的文章

在MVCC中,读和写不会冲突,每行都会记录多个版本,以避免多个事务之间的竞争。 以空间换时间的思想,大大提升了读写性能。

mysql数据库基础教程_数据库mysql知识点整理_mysql数据库基础知识

MVCC主要是通过undo log版本链来实现的。

首先对undo log有一个基本的了解

撤消日志

undo log主要用于事务回滚时恢复原始数据

mysql执行一条sql语句时,会保存一条与undo日志逻辑相反的日志。 因此,undo日志中记录的也是逻辑日志。

当SQL语句为 时,本次插入的主键id会记录在undo log中。 当事务回滚时,这个id就足够了。

当sql语句为 时,修改前的数据会记录在undo log中。 当事务回滚时,再次执行即可得到原始数据。

当sql语句为 时,删除前的数据会记录在undo log中。 当事务回滚时,原始数据就足够了。

数据库事务四大特性中的原子性,即事务不可分割,要么全部成功,要么全部失败,底层通过undo log来实现。 当一个步骤失败时,前一个事务的语句将被回滚。

如果你对数据库中的日志完全不熟悉,可以看我的另一篇文章数据库日志——、redo log、undo log读写

行的隐藏列

数据库的每一行上,除了存储真实数据外,还有3个隐藏列——、。

, 电话号码

如果当前表有整型主键,则为主键的值。

如果没有整数类型的主键,mysql会选择一个非空整数类型的唯一索引作为字段顺序。

如果mysql没有找到,它会自动生成一个自增整数作为值。

这与今天的 MVCC 有什么关系?

我不能说没有什么,只能说与此无关。

数据库mysql知识点整理_mysql数据库基础教程_mysql数据库基础知识

, 交易号

在一个事务开始执行之前,mysql会为该事务分配一个全局自增的事务id。

之后,当事务对当前行进行添加、删除、修改时,都会在 中记录自己的事务id。

,回滚指针

当事务修改当前行时,会将旧的数据写入undo log,然后将新的数据写入当前行,而当前行指向刚才的undo log,这样就可以找到之前的版本行。

当一直有事务改变该行时,就会一直产生undo log,最终形成一条undo log版本链。

撤消日志版本链

首先,我们使用以下语句创建一个表

CREATE TABLE `student` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR ( 255 ) NOT NULL,
	`age` INT ( 11 ) NOT NULL,
  PRIMARY KEY ( `id` ) USING BTREE 
) ENGINE = INNODB;

现在启动第一个事务,事务id为1,执行下面的语句。

INSERT INTO student VALUES ( 1, "a", 24 );

那么当前线路的示意图如下:

mysql数据库基础知识_数据库mysql知识点整理_mysql数据库基础教程

因为数据是新插入的,所以它指向的undo log是空的。

然后启动第二个事务,分配的事务id为2,执行如下修改命令。

UPDATE student SET NAME = 'b' WHERE id = 1;

该图现在变为:

mysql数据库基础教程_数据库mysql知识点整理_mysql数据库基础知识

当启动第三个事务且分配的事务id为3时,执行以下修改命令。

UPDATE student SET age = 25 WHERE id = 1;

示意图变为:

mysql数据库基础知识_mysql数据库基础教程_数据库mysql知识点整理

当每个事务更改该行时,都会生成一条undo log来保存之前的版本,然后新版本将指向刚刚生成的undo log。

因此,这些不同版本的undo log可以串联起来,形成undo log版本链。

首先需要了解快照读和当前读

快照读取:简单查询,即排除…共享模式下的锁定,…for ,可能会读取数据的历史版本。

当前读:以下语句都是当前读,始终读最新版本,锁定最新版本读。

当事务执行每次快照读或者事务第一次执行快照读时,即生成一致视图。

的作用是确定undo log版本链中哪些数据对当前事务可见。

包含以下重要参数:

m_ids

创建时,会收集 mysql 中所有未提交的事务 ID。

m_ids 中的最小值

mysql将分配给下一个事务的事务id不是m_ids中的最大值。

即创建此交易的交易ID

简要示意图如下:

mysql数据库基础教程_mysql数据库基础知识_数据库mysql知识点整理

那么当事务执行快照读时,可以通过以下规则来判断undo log版本链上的哪个版本数据是可见的。

如果当前的undo log版本是,则说明该版本对应的事务在生成之前已经被提交了,所以是可见的。

如果当前undo log的版本≥,说明该版本对应的事务是在生成后才开始的,所以是不可见的。

如果当前undo log的版本为ε[,),如果在这个范围内,则需要判断是否在m_ids中:

m_ids中,描述版本对应的事务尚未提交,因此不可见。

如果不在m_ids中,则说明该版本对应的事务已经提交,因此可见。

如果当前undo log的版本=,则说明该事务正在访问自己修改的数据,因此是可见的。

当判断undo log版本链表的头节点数据不可见时,用它来查找之前的版本,然后进行判断。 如果整个链表都没有找到可见数据,则说明当前查询找不到数据。

mysql数据库基础教程_mysql数据库基础知识_数据库mysql知识点整理

四种隔离级别下MVCC的区别

在Read级别,事务总是读取最新的数据,所以根本不使用历史版本,所以MVCC在这个级别不起作用。

在某种程度上,事务总是按顺序执行。 写会加写锁,读会加读锁。 根本没有使用MVCC,所以MVCC在这个级别不起作用。

真正兼容MVCC的隔离级别是Read(RC)和Read(RR)

RC 和 RR 级别的 MVCC 之间的区别在于生成的频率不同。

在RC层面,当前事务总是想读取其他事务提交的数据,因此每次执行快照读时都会生成当前事务事务,m_ids会实时更新,那些已提交的事务就会被发现及时。

在RR层面,当前事务当然也可以读取其他事务提交的数据,但是为了避免不可重复读,只有在执行第一个快照读时才会产生,后续的快照读总是会被执行。用过应该。

举个栗子:

在 RC 级别

一开始,事务id为1的事务向表中插入一条数据,版本链如下:

数据库mysql知识点整理_mysql数据库基础知识_mysql数据库基础教程

此时启动事务id为2的事务,并关闭自动提交模式。首先执行一条*查询,生成如下

m_ids={2},=2,=3,=2

因为有了这条数据,就意味着这个版本对应的事务在生成之前就已经被提交了,所以是可见的。

因此事务2可以直接找到数据。

现在启动事务3,事务id为3,将该条数据的名称改为b,自动提交。 版本链如下:

数据库mysql知识点整理_mysql数据库基础知识_mysql数据库基础教程

这时事务2*再次查询,由于是RC级别,所以会再次生成。 此时,如下:

m_ids={2},=2,=4,=2

由于ε[2,4)的最新版本不在m_ids中,说明该版本的数据已经提交,所以是可见的,所以事务2可以找到最新的数据。

在 RR 级别:

当事务2*再次查询时,不会生成,而是继续使用第一代:

m_ids={2},=2,=3,=2

由于最新版本的≥,表示该版本对应的交易是在生成后才开始的,所以是不可见的。

所以继续去找以前的版本,以前的版本

因此,事务2只能查询旧版本的数据,并且两次查询是一致的,避免不可重复读。

​​​​

数据库mysql知识点整理_mysql数据库基础知识_mysql数据库基础教程

好了,今天的主题就讲到这里吧,不管如何,能帮到你我就很开心了,如果您觉得这篇文章写得不错,欢迎点赞和分享给身边的朋友。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注