Mysql的redo日志和undo日志以及事物的传播性和隔离级别

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务的特性(ACID)

  1. 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。比如A向B转账100元,总共有两步,一、A的账户减去100,二、B的账户加上100;转账过程中,这两步要么都成功,要么都失败,不会出现一步成功另一步失败。

  2. 一致性: 执行事务前后,数据保持一致;比如A账户有100元,B账户有0元,现在A向B转账100元:转账前,A账户100元,B账户0元;转账后,A账户0元,B账户100元;转账前后,这些数字在数据库的实际变化和我们的主观认识相同,这时数据就是一致的。

  3. 隔离性: 并发访问数据库时,一个事物不被其他事物所干扰;对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

  4. 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

innodb事务日志包括redo log和undo log。redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作。数据库事务的隔离性由锁机制实现,原子性、持久性和一致性是依靠redo和undo的日志实现的

一、undo log

undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

undo是一种逻辑日志,有两个作用:

  • 用于事务的回滚
  • MVCC

undo日志,只将数据库逻辑地恢复到原来的样子,在回滚的时候,它实际上是做的相反的工作,比如一条INSERT ,对应一条 DELETE,对于每个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。undo日志用于事务的回滚操作进而保障了事务的原子性

二、redo log

redo log不是二进制日志(binlog)。虽然二进制日志中也记录了innodb表的很多操作,也能实现重做的功能,但是它们之间有很大区别。二进制日志是在存储引擎的上层产生的,不管是什么存储引擎,对数据库进行了修改都会产生二进制日志。而redo log是innodb层产生的,只记录该存储引擎中表的修改。并且二进制日志先于redo log被记录

重做日志(redo log)用来保证事务的持久性。**Redo log的主要作用是用于数据库的崩溃恢复。**它可以分为以下两种类型:

  • 物理Redo日志

  • 逻辑Redo日志

在InnoDB存储引擎中,大部分情况下 Redo是物理日志,记录的是数据页的物理变化。而逻辑Redo日志,不是记录页面的实际修改,而是记录修改页面的一类操作,比如新建数据页时,需要记录逻辑日志。关于逻辑Redo日志涉及更加底层的内容,这里我们只需要记住绝大数情况下,Redo是物理日志即可,DML对页的修改操作,均需要记录Redo.

下面以一个更新事务为例:

第一步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。

第二步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。

第三步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式。

第四步:定期将内存中修改的数据刷新到磁盘中。

下面是redo log + undo log的简化过程,便于理解两种日志的过程:

假设有数据库有A、B两条数据,值分别为1,2.
1. 事务开始
2. 修改前记录A=1到undo log buffer
3. 修改A=3
4. 记录A=3到 redo log buffer
5. 修改前记录B=2到 undo log buffer
6. 修改B=4
7. 记录B=4到redo log buffer
8. 将undo log写入磁盘
9. 将redo log写入磁盘
10. 事务提交
11. 将数据异步写入磁盘

可以看出,数据事物提交后异步写入磁盘的。为什么事物提交时log写入磁盘了,数据还没写入磁盘?因为数据写磁盘是随机写的,多条数据可能存在磁盘的不同位置,磁盘需要寻址(耗时),因此异步写入。写log就不需要寻址、速度就很快吗?是的,写log速度很快,因为是顺序写的,在磁盘的连续空间上;并且实际上redo log和undo log一起都记录到了redo log中,只需要一次IO将redo log顺序写入磁盘即可,所以很快。事物提交后,即使数据库宕机了,重启后也会自动通过redo log恢复数据到最新的状态,从而保证持久化。

三、Isolation(事务的隔离级别)

事物的隔离级别在spring框架层面有五种:

public enum Isolation {
    DEFAULT(-1), //使用后端数据库默认的隔离界别,MySQL默认:REPEATABLE_READ,Oracle默认:READ_COMMITTED
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}    

其中DEFAULT,是由当前数据库决定具体是什么隔离级别。

四种隔离级别可能导致的问题:

1、Serializable (串行化):最严格的级别,事务串行执行,资源消耗最大;

2、REPEATABLE READ(重复读) :保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失。

3、READ COMMITTED (提交读):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。

4、Read Uncommitted(未提交读) :事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。

通俗解释:

脏读:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。

image-20201030232712067

也就是说,当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据。

不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。

image-20201030232737273

也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。

幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。

image-20201030232755478

也就是说,当前事务读第一次取到的数据比后来读取到数据条目不一致。

四、Propagation (事务的传播属性)

在Spring中,事物有7中传播方式。

public enum Propagation {
    REQUIRED(0),      // 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。spring默认。
    SUPPORTS(1),      // 支持当前事务,如果当前没有事务,就以非事务方式执行。
    MANDATORY(2),     // 支持当前事务,如果当前没有事务,就抛出异常。
    REQUIRES_NEW(3),  // 新建事务,如果当前存在事务,把当前事务挂起。
    NOT_SUPPORTED(4), // 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    NEVER(5),         // 以非事务方式执行,如果当前存在事务,则抛出异常。
    NESTED(6);        // 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
}    
@Service
public class ServiceA {
    @Transactional(propagation = Propagation.XXXXXXX)
    public void methodA{
         ServiceB.methodB();//Propagation.XXXXXXX
    }
}    

1: PROPAGATION.REQUIRED

支持当前事务,如果当前没有事务,就新建一个事务。

比如说,ServiceB.methodB的事务级别定义为PROPAGATION.REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行

3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常

4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。

7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。

关注公众号,输入“java-summary”即可获得源码。

完成,收工!

传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值