Mysql数据库——事务的特性,隔离级别,并发事务带来的问题,事务的实现,MVCC

事务

1.ACID特性

1.1.原子性(Atomicity)

一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

原子性是由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql。

1.2.一致性(Consistency)

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

一致性是由其他三大特性保证,程序代码要保证业务上的一致性。

1.3.隔离性(Isolation)

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

隔离性由MVCC来保证。

1.4.持久性(Durability)

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

持久性由内存+redo log来保证,mysql修改数据的同时在内存和redo log记录这次操作,如果发生宕机可以从redo log 中恢复。

举个栗子:

InnoDB redo log 写盘,InnoDB事务进入prepare状态,如果前面prepare成功,binlog写盘,再继续将事务日志持久化到binlog。如果持久化成功,那么InnoDB事务则进入commit状态(在redo log里面写一个commit记录)。

2.并发事务带来的问题

2.1.脏读

一个事务读取了另外一个事务未提交的数据。

之所以出现脏读,是因为对于"select"操作没有限制。

2.2.不可重复读

不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。

之所以出现不可重复读,是因为对于"update"操作没有限制。

2.3.幻读

幻读是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

之所以出现幻读,是因为对于“insert”和“delete”操作没有限制。

3.隔离级别

3.1.读未提交(read uncommitted)

一个事务可以读取另一个未提交事务的数据。脏读、不可重复读和幻读均不可避免。

3.2.读已提交(read committed)

一个事务要等另一个事务提交后才能读取数据。可避免脏读,不可重复读和幻读不可避免。

3.3.可重复读(repeatable read)Mysql的默认隔离级别

一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但不可以访问成功修改的数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。此隔离级别可有效防止不可重复读和脏读。

3.4.串行化(serializable)

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。

一般来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。Mysql的默认隔离级别为可重复读。

4.事务的实现

1.1.基于锁的并发控制(LBCC)

当一个事务在操作数据库时,会将该条数据锁定,其他事务无法操作,直到当前事务完成,解除锁,其他事务才可以进行操作。这样可以实现事务,但是由于上锁,其他需要操作该条数据的事务都会阻塞,就会大大影响实际效率。这种方式就叫做基于锁的并发控制Lock Based Concurrency Control (LBCC)。

1.2.多版本并发控制(MVCC)

上述LBCC有着很明显的缺点,那就是效率很低。为此,我们引入了另一种方案——当一个事务操作数据库时,比如查询一条数据,我就对这条数建立一个备份或者快照,然后直接读取这个快照。这种方案就叫做多版本并发控制Multi Version Concurrency Control(MVCC)。

那么MVCC又是靠什么来实现的呢?其实他是由undo log版本链以及Read View机制来实现的。那么下面就来介绍一下。

1.2.1.undo log版本链

当我们插入一条数据的时候,每条数据会自动添加两个隐藏的字段,一个是trx_id(事务的id),另一个是roll_pointer(回滚指针)。

trx_id中存储着操作这条数据的事务的id值,roll_pointer中存储的是该条数据的上一版本的地址。

举个栗子:

现在我们有3个事务id分别为100,200,300,id=100的事务插入了一条数据,这个时候这条数据的事务id为100,因为之前没有这条数据,所以回滚指针为空。这个时候id=200的事务来修改了这条数据,此事这个事务id为200,而回滚指针指向了id=100的事务。最后id=300的事务又来修改了这条数据,这个时候事务id为300,回滚指针指向了id=200的事务。如图所示:

在这里插入图片描述

1.2.2.Read View

我们需要解决的问题就是,需要判断一下版本链中的哪个版本是当前事务可见的。所以设计InnoDB的设计者提出了一个ReadView的概念,这个ReadView中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids,列表的最小值为min_id,最大值为max_id。

这样在访问某条记录时,只需要按照下边的步骤判断该记录在版本链中的某个版本(trx_id)是否可见:

注:事务id的是自增的,所以可以进行以下判断~

1、trx_id < min_id表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。

2、trx_id > max_id 表明生成该版本的事务在生成ReadView 后才生成,所以该版本不可以被当前事务访问。

3、min_id < trx_id < max_id

(1)trx_id在m_ids中:说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问。

(2)trx_id不在m_ids中:说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

对于删除的情况,可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改为删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示记录已经删除,在查询时按照上面的规则查到对应的记录,如果delete_flag标记为true,意味着记录已被删除,则不会返回数据。

在这里插入图片描述

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本,如果最后一个版本也不可见的话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。

在MySQL中,读已提交(read committed)和可重复读(repeatable read)隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同,那么我们可以继续来看一下。

1.2.2.1.读已提交

使用读已提交隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。

1.2.2.2可重复读

使用可重复读隔离级别的事务在第一次查询后生成的ReadView会沿用到事务结束状态,中间不会再生成任何ReadView。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值