Mysql事务隔离级别及ACID实现原理

Posted 小艾路西里

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql事务隔离级别及ACID实现原理相关的知识,希望对你有一定的参考价值。

1. MySql事务简介

事务是一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
事务只和DML语句(sql语句中的curd)有关,也可以说DML语句才有事务

2. 事务的用法

mysql默认开启了自动提交,在执行insert,update,delete语句时候每一条sql语句就是一个事务

(1) 事务涉及的关键字

① 用begin,rollback,commit来实现事务

开启事务使用 begin; 或 start transaction;

回滚:rollback;
回滚后,事务结束,但是会恢复事务开始前的数据

提交:commit;
提交后,事务结束,实现数据持久化

② 改变mysql的自动提交模式

MySql默认是自动提交的,也就是你提交一个DML语句,它就直接执行

set autocommit=0 禁止自动提交 set autocommit=1 开启自动提交

当使用 set autocommit=0 后,所有的SQL都将做为事务处理,直到你用commit确认或rollback结束

查看自动提交 : show global variables like ‘autocommit’

在事务开启后可执行任何其他操作,遇到commit才会写入至硬盘,遇到rollback会回滚到事务开启的那个时候

(2) 事务的使用

为了打开事务,允许在COMMIT和ROLLBACK之前多条语句被执行,我们需要做以下两步:

① 设置MySQL的autocommit属性为0,默认为1

② 使用 Begin或 start transaction语句显式的打开一个事务

备注:如果已经打开一个事务,则set autocommit=0不会起作用
因为START TRANSACTION会隐式的提交当前所有的更改,结束当前的事务,并打开一个新的事务

事务使用模板:

Begin; -- 开启事务
 ..... -- DML语句
 .....
Commit; -- 提交事务
Except -- 如果出现异常
  RollBack; --回滚数据
End; -- 结束事务

JDBC中使用事务

try{    
    connection.setAutoCommit(false);  //不自动提交  
    //......执行jdbc代码
    //sql1,sql2,sql3    
    connection.commit(); //提交事务  
    }catch(Exception e){    
         connection.roolback();   //事务回滚
    }finally{   
         //清理资源  
         connection.setAutoCommit(true);  //改回自动提交  
   }

3. 事务四大特征(ACID)

原子性 A:事务是业务的最小单位,同一个事务内部的一组操作必须全部执行成功(或者全部失败)

一致性 C:事务执行后的结果是和预设的规则完全符合的,不会因为出现系统意外等原因和预测的结果不一致,其它的三个属性都为了保证一致性而存在

隔离性 I:事务A和事务B之间不相关,即具有隔离性

持久性 D:是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)

4. 事务ACID特性的实现原理

在了解实现原理前,先要理解一些专业性词汇:redo log(重做日志)、undo log(回滚日志)、lock(锁)、MVCC(并发控制的方法)

Innodb架构的内存分布(只用看圈起来的部分)在这里插入图片描述

(1) 原子性的实现

实现原子性特性的重点是 undo log(回滚日志),undo log用于记录数据被修改前的信息,存储反向的sql语句

以一个update语句事务回滚来说明redo log

begin;
update 修改内容 where id = 8;  -- 这时BufferPool的内容已经被修改了 
rollback;

① update语句执行,对应修改BufferPool中的页数据,使其变为脏页,交予Flush List控制

② update语句生成一个redo log对象,存储在 redo log buffer中

③ 生成 undo log (存储反向内容,相当于是update 原来的的数据 where id = 8)

④ 执行rollback语句,通过执行对应undo log中的内容达到回滚

(2) 持久性的实现

实现持久性特性的重点是 redo log(重做日志)

redo log日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中

当事务提交之后会把所有修改信息都会存到该日志中

以一个update语句的事务提交执行流程来说明redo log

begin;
update 修改内容 where id = 8;  -- 这时BufferPool的内容已经被修改了 
commit;

① update语句执行,对应修改BufferPool中的页数据,使其变为脏页,交予Flush List控制

② update语句生成一个redo log对象,存储在 redo log buffer中

③ 生成 undo log

④ redo log 的内容持久化(事务commit的时候,如果事务rollback了则不会进行这一步)

⑤ bin log 持久化(binlog用于记录用户对数据库更新的SQL语句信息)

⑥ 返回客户端 修改成功

redo log 是如何做到数据恢复的?

假设在该事务执行的过程中,MySql挂掉了,当重启MySql后,读取磁盘中的该数据,那么这时读取的数据还是原始的状态

解释:
① redo log是存放在磁盘上的,所以磁盘上既有原来的数据,也有对应的redo log日志文件

② 当事务执行后,redo log的内容也会对应持久化在磁盘上

③ 当MySql重启后,假设再对该数据进行查询,MySql会同时提取磁盘中对应的 redo log 和 页数据到BufferPool中,并自动用redo log的内容对该数据页进行恢复

如果是事务进行了rollback,那么会根据 undo log的内容,反向操作数据,达到回滚的目的

如果不使用redo log的方式,就是直接到磁盘中去更新,但是磁盘是按页存取,而一页的数据很多,修改可能只是一小段数据,但是会对整页进行读写,效率也会比使用redo log低

补充:innodb提供了三种redo log持久化方式,可以设置
在这里插入图片描述

(3) 隔离级别及隔离性的实现

MySQL隔离级别有以下四种(级别由低到高):

① read uncommited(读未提交)

② read commited(读已提交)

③ repeatable read(可重复读)

④ serializable (串行化读)

级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大

可靠性性高的,并发性能低
可靠性低的,并发性能高

以下语句可以设置MySql事务的隔离级别,默认是第3级别

SET session transaction isolation level read committed;

① read uncommited (读未提交 最低级别)

就是一个事务对BufferPool数据页的读写操作还未提交,但其他事务可以访问该数据页

事务中的修改即使还没提交,对其他事务是可见的

这时事务可以读取未提交的数据,但是会造成脏读(读取的是不稳定的数据)

优点:读写并行,性能高
缺点:造成脏读

无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据

但由于另外一个事务还没提交,所以他随时可能会回滚,那么必然导致你更新(查询)的数据就没了

② read commited (读已提交)

一个事务提交了之后,另外的事务可以去读取数据

InnoDB在 READ COMMITTED读取数据不加锁而是使用了MVCC机制,也就是采用了读写分离机制

该级别会产生不可重读以及幻读问题

不可重复读:同一个事务内多次读取同一行数据的结果不一样

出现不可重复读的问题和MVCC机制有关系

因为MVCC机制会在该隔离级别下每次 select的时候新生成一个版本号,也就是copy一个副本,所以每次select的时候读的不是同一个副本而是不同的副本

简单来说就是一个事务执行时,如果读取一个数据页多次
在中途的时候,如果这个数据页被其他事务改变了,但其他事务改变该数据后提交了,后续语句就可以读到,就造成了该问题

幻读:在同一事务内,后续读取读到之前没有的一批数据

例如:
① 事务 A 里有一个条件查询的语句 select name from t where id > 10,它进行了一次范围查询,查到了 10 行数据;
② 事务 B 往里面add了一批数据并提交
③ 事务 A 再次查询的时候,发现查到了 15 条,其中 5 条是之前没见过的。事务 A 以为出现幻觉了,为什么多出来了5条,这就是幻读

脏读与幻读的区别:脏读主要是因为另外的事务执行了update操作,而幻读是insert操作

③ repeatable read(可重复读 MySql的默认级别)

在这种级别下,在一个事务内的多次读取的结果是一样的

该级别可以避免,脏读,不可重复读等查询问题

mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC

方式一 读写锁:读锁+写锁 读锁可以共享

只要没释放读锁,在第二次读的时候还是可以读到第一次读的数据,从而解决不可重复读的问题

优点:实现起来简单

缺点:无法做到读写并行

方式二 MVCC:只加写锁,并且读取的是数据页的副本

为什么能可重复读?因为多次读取只生成一个版本,读到的自然是相同数据。

优点:读写并行

缺点:实现的复杂度高

锁机制

InnoDB 通过锁机制来保证事务间的隔离性

锁机制的基本原理可以概括为:
① 事务在修改数据之前,需要先获得相应的锁;
② 获得锁之后,事务便可以修改数据;
③ 该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。

MySQL锁的分类

① 按操作类型可以分为读锁(共享锁S)和写锁(排它锁X)

读锁:对同一份数据,多个读操作可以同时进行而不会互相影响
写锁:当前操作没有完成之前,会阻塞其他读锁和写锁

②按操作粒度分为行锁、表锁、页锁。

行锁指对某行数据加锁,是一种排它锁

表锁指对当前操作的整张表加锁,实现简单,资源消耗较少

页锁的锁定粒度介于行锁和表锁之间,一次锁定相邻的一组记录

补充:

表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般不常用

行锁则是锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强

MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁

MVCC

MVCC称为多版本并发控制 ,读取数据时以类似快照的方式将数据保存下来,这样读锁和写锁就不冲突了,因为不同事务只能看到当前版本的数据,版本链的思想

MVVC只在RC(读已提交)和RR(可重复读)级别使用

InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和undo log(回滚日志),隐藏列包括了该行数据的版本号、删除时间、指向undo log的指针等等

聚集索引有两个必要的隐藏列

trx_id : 用于记录对数据进行操作的事务的id

roll_point : 当有事务对数据进行修改时,会把老版本数据存储进undo log日志中,roll_point指针会指向该数据的位置,也就是老版本的位置,从而实现了版本链

在这里插入图片描述
当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现 MVCC;

这种读取方式就被称为Read View策略

上述的版本链和Read View策略合起来就形成了MVVC机制

read commited(读已提交)和repeatable read(可重复读)的区别就在于生成的Read View策略不一样

read commited(读已提交)的read view策略:

事务开启的时候就会创建read view,用于记录当前未提交的事务id,也就是活动中的事务,排序成一个事务id数组(可以称为read view数组)

当有事务需要操作数据时,就会获取该事务的id,对比read view数组中的事务id,通过比较知道该事务是否已提交,如果提交了则可以操作数据

例如read view中存储的事务id是[10,11,…,19,20],如果当前事务id是9,那么就小于read view中的id,说明不是活动事务,而且是已经提交的,说明可以访问数据(读已提交)

当然如果当前事务id>20,那么说明该事务在read view之后出现,在当前活动事务的后面,还未提交,不能访问数据

如果是事务10<id<20,那说明当前事务正是read view存储的活动事务,还未提交,也不能访问数据

不能访问数据的事务,会获取roll_point指针,去和上一个版本的数据对比,对比的即是上一个版本的事务id,再进行read view策略,从而知道能否读老数据

repeatable 可重复读的read view策略:

该级别下,事务每次查询都会生成一个新的read view数组,而RC级别是复用之前的read view数组

④ serializable (序列化读/串行化读)

使用了排他锁,也就是无论读写都会使用锁,不允许读读共享

优点:解决了所有问题

缺点:需要事务排队(使用排他锁)

隔离级别总结
在这里插入图片描述

(4) 一致性的实现

一致性是指:数据库总是从一个一致性的状态转移到另一个一致性的状态

就像ACID特性一开始总结的一样,一致性是其他三个共同完成的目标,在各种异常发生的时候,能做到恢复,能避免脏读、幻读、不可重复读等问题

所以一致性的实现即是其他三个属性共同的实现

ACID实现原理的总结:

原子性:使用 undo log(回滚日志) ,从而达到回滚

持久性:使用 redo log(重做日志),保障已提交事务的持久化特性,也能达到故障后恢复

隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行,read view策略

一致性:利用前三个特性,通过回滚,以及恢复,和在并发环境下的隔离做到一致性

以上是关于Mysql事务隔离级别及ACID实现原理的主要内容,如果未能解决你的问题,请参考以下文章

mysql事务隔离级别的实现原理

数据库事务的ACID及隔离级别

Mysql的事务特性,四种隔离级别及原理

MySQL 事物及隔离级别

Mysql的事务特性,四种隔离级别及原理

Mysql的事务特性,四种隔离级别及原理