Linux_MySQL(核心) 事务(事务属性、隔离性与隔离级别、隔离性的理解、MVCC、隔离级别的实现ReadView)

1.事务的概念

  1. 事务:是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。
    此外,事务还规定不同的客户端看到的数据是不相同的。(隔离性)

  2. 事务主要用于处理数据量大,复杂度高的业务场景。这个操作需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。(多条的MySQL之间存在逻辑相关性)

注意:一个MySQL数据库不可能只有一条事务运行,在同一时间可能有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求。这就要求一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下的几个属性

  1. 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  2. 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  3. 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预的工作。
  4. 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )

MySQL事务通过保证原子性、持久性、隔离性来达到一致性。
这四种属性简称为:ACID

注意:一致性原则是由数据库和具体的业务逻辑决定的,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。

  1. 数据库设计事务:本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。

  2. 如果数据库没有设计事务,具体多线程下的问题需要使用者考虑,编程模型就变得更加复杂

存储引擎:
在 MySQL 中只有使用了 InnoDB 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
在这里插入图片描述

事务的提交方式

  • 手动提交
  • 自动提交:默事务的提交方式为自动

在这里插入图片描述

--用 SET 来改变 MySQL 的自动提交模式:
SET AUTOCOMMIT=0; --禁止自动提交

一个事务在begin(开始)到commit(提交)之间的所有SQL命令

这台机器的隔离性为:
在这里插入图片描述
所以在事务提交前另一台机器上是看不到修改的,如果MySQL在执行时崩溃没有commit,MySQL会自动回滚。
在这里插入图片描述

提交后:
在这里插入图片描述
观察MySQL回滚

在这里插入图片描述
这里第三条插入消息就被回滚掉了,最后提交后,数据库没有第三条信息。

需要注意:

  1. 如果没有设置savepoint 回滚默认到开头。
  2. 用户begin开始后,就需要手动commit 这与MySQL是否设置自动提交无关。(默认变成手动提交)
  3. 在自动提交下,单条SQL语句默认就是一个事务。所以如果关闭默认自动提交,如果SQL语句结束后没有commit,下次再查看这张表时数据没有在表上(没有持久化)。(select 有特殊情况MVCC)

2.隔离性与隔离级别(脏读、幻读、不可重复读、可重复读)

首先我们已经能确定

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
  • 一个事务可能由多条SQL构成,执行中出现问题,可以随时回滚,单个事务,对用户表现出来的特性,就是原子性
  • 在多个事务各自执行多个SQL的时候,有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。

数据库为了保证事务在执行过程中尽量不受干扰,提出了隔离性。

数据库允许事务收到不同程度的干扰,就有了一种重要特征:隔离级别

隔离性的具体实现方案就是通过具体的隔离级别完成的。

具体隔离级别:

  • 读未提交【Read Uncommitted】:在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果,类似一种动态更新。实际生产中不可能使用这种隔离级别的。基本没有隔离性
    一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读(dirty read)

  • 读提交【Read Committed】:数据不提交,其他的事务看不到。:一个事务只能看到其他的已经提交的事务所做的改变。
    这种情况可能出现,在相同的表,不同的事件点查看表结果不同,这种情况称为不可重复读

  • 可重复读【Repeatable Read】:这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。一个事务修改表并提交,另一个事务如果也在处理这张表,无论第一个事务是否提交,第二个事务都无法看到表的变化。
    大部分数据库在重复读时无法屏蔽其他事务插入操作。因此在多次查询时,如果遇到其他事务向表插入操作,可能出现多次查询结果不同,这种情况成为幻读-----MySQL通过(Next-Key锁(GAP+行锁)解决了这个问题)

  • 串行化【Serializable】:这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。(有事务进行表读取,当另有事务进行表修改时需要等待读事务结束才能进行,否则会卡住)
    但是可能会导致超时和锁竞争

查看隔离级别

 SELECT @@global.tx_isolation; --查看全局隔级别,每次连接时采用隔离级别
 SELECT @@session.tx_isolation; --查看会话(当前)全局隔级别,只对当前会话有效
 SELECT @@tx_isolation; --默认同上

修改隔离级别

-- 设置当前会话 or 全局隔离级别语法 
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

如果不同事务对同一张表进行修改操作,会加锁保证数据安全。

3.隔离性的理解

  • 读-读 :不存在任何问题,也不需要并发控制
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
    第一类丢失:因为多个事务同时修改表,某些事务回滚导致有些事务修改操作被覆盖
    第二类丢失:与第一类丢失相似,不同的是回滚的事件点不同,这里不多介绍。

在数据库中最常见的情景是读写场景。这里重点考虑

MVCC多版本并发控制

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

在MySQL中每个事务都有事务ID,事务无论如何同时到来,一定有先有后。

对数据库表修改时,每个修改都会保留一个版本,这个版本与事务ID相关联。读操作只读该事务开始前的数据库的快照。
快照:原表通过拷贝的形式形成备份,这个备份表就称为原表的一份快照。

MVCC解决了数据库:

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

理解MVCC

3个记录隐藏字段

当创建表时MySQL会默认添加的三列隐藏信息。

  • 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标记为删除, 既记录被更新或删除并不代表真的删除,而是删除flag变了。

undo日志

  • MySQL以服务进程的方式在内存上运行,索引,事务,隔离性,日志等,都是在内存中完成的。

  • 即在 MySQL 内部的相关缓冲区中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中。

  • unod日志可以简单理解为,MySQL 中的一段内存缓冲区,用来保存日志数据

模拟MVCC

这里以简单的学生表为例

在这里插入图片描述
根据上面的分析可知MySQL会添加隐藏字段,开始时三个隐藏字段的值如下图

在这里插入图片描述

现在有一个事务10,对student表中记录进行修改(update):将name(abs)改成name(aaa)。

事务10因为要修改表,所以要先加锁。

修改时,先将表拷贝到undo log缓冲区中,再修改原表name。更新DB_TRX_ID为10号事务,DB_ROLL_PTR回滚指针指向undo日志中原来拷贝的数据。

之后事务10提交,释放锁。
在这里插入图片描述

此时事务11也要对表的年龄进行修改;
这次修改应该修改的是最新数据,将之前10号事务添加的节点拷贝到undo log中,再对原表进行修改。

更新DB_TRX_ID为11号事务,DB_ROLL_PTR回滚指针指向undo日志中原来拷贝的数据。
在这里插入图片描述
这样设计就形成了历史版本链。

当事务11没有提交或者回滚时,就可以通过这张链表来进行回退。除了最新的版本,其余的其他版本就可以成为快照。

类似的:

  • delete操作回滚与修改操作回滚操作类似。只不过MySQL不会真正的删除,而只是设置标志位,方便数据回滚回复。

  • insert 操作:insert是插入,也就是之前没有数据,那么insert也就没有历史版本。但是一般为了回滚操作,insert的数据也是要被放入undo log中,如果当前事务commit了,那么这个undo log 的历史insert记录就可以被 清空了

undo log中的版本链如果还有事务与其有关,会一直存在。

select操作不会修改数据,但时select要决定读取那个版本的数据

  • 当前读:读取最新的记录,就是当前读。增删改,都叫做当前读。一定要加锁保护。select也有可能当前读,比如:select lock in share mode(共享锁), select for update。select如果选择当前读,也需要加锁类似串行化的方式读取。
  • 快照读:读取历史版本(一般而言),就叫做快照读。快照读读取的时历史版本,一定不会被修改,不需要加锁。

select读取方式与隔离级别有关。多个事务在执行中,CURD操作是会交织在一起的。那么,为了保证事务的“有先有后”,应该让不同的事务看到它该看到的内容,这就是所谓的隔离性与隔离级别要解决的问题。隔离级别约束了事务能够看到的历史版本。

隔离级别的实现Read View

Read View 在 MySQL 源码中,就是一个类。当MySQL事务进行快照读时创建对象,生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。

Read View,本质是用来进行可见性判断的。当某个事务执行快照读的时候,对该记录创建一个 Read View 读视图(创建对象)。

把它作为比较条件,用来判断当前事务能够看到哪个版本的数据。

既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。

Read View简化结构:

class{
	/** 创建视图时的活跃事务id列表*/ 
	ids_t m_ids;//一张列表,用来维护Read View生成时刻,系统正活跃的事务ID
	
	trx_id_t m_up_limit_id;//记录m_ids列表中事务ID最小的ID
	
	trx_id_t m_low_limit_id; //ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

	creator_trx_id //创建该ReadView的事务ID
};

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID以及当前快照读的 ReadView。

当前快照读,如何读到当前版本记录:在这里插入图片描述

隔离性的本质是通过ReadView类对象,通过对比事务ID来决定事务提交的先后,进而确认事务的可见性。

如果查到不应该看到当前版本,接下来就是遍历下一个版本,直到符合条件,即可以看到。

事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读,决定该事务后续快照读结果的能力。
delete同样如此。因为首次快照读时会形成ReadView。

读提交【Read Committed】与可重复读【Repeatable Read】的区别

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

在RR级别下

  1. 某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来。此后不会对ReadView做任何更新。
  2. 此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
  3. 快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见。

RC级别下

  1. 事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。保证了每次读取时看到的都是最新的数据。
  2. RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。
  3. RC每次快照读,都会形成Read View,所以,RC才会有不可重复读问题。
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值