ACID

事务具有4个特征,分别是原子性、一致性、隔离性和持久性,简称事务的ACID特性;

一、原子性(atomicity)

一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性

二、一致性(consistency)

  事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。

  如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态。

  假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库 数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证。

三、隔离性(isolation)

  事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。

在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化

1读未提交(Read Uncommited)该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取

    脏读(Drity Read):某个事务已更新一份数据,另一个事务在此读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

2授权读取也称为已提交读(Read Commited),授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。

    不可重复读(读已提交)解决了脏读的问题,他只读取已经提交的事务。

    不可重复读(Non-repeatable read)在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个新的事务跟新的原有数据。(跟新

3、可重复读(Repeatable Read)

就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;

    幻读(Phantom Read)在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(row)数据,而另一个事务在此时插入了新的几列数据,在前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的(插入或者删除)。

4、串行化

是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。

四、持久性(durability)

一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。--即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

-----------------------------------------------------------------------

不可重复读
在一个事务中多次读取同一个数据时,结果出现不一致。

 

解决:在 MySQL InnoDB 中,Repeatable Read 隔离级别使用 MVCC 来解决不可重复读问题。

幻读
在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。

 

解决:在 MySQL InnoDB 中,Repeatable Read 隔离级别不存在幻读问题,对于快照读,InnoDB 使用 MVCC 解决幻读,对于当前读,InnoDB 通过 gap locks 或 next-key locks 解决幻读。

如何用锁的方式解决脏读,幻读,不可重复读。

脏读:

对数据进行修改的时候,加上排他锁, 

幻读:

对于新增的幻影数据, 当前事务的行锁锁不到这个暂时还未新增的幻影数据,引入间隙锁来解决这个问题.

间隙锁的大致解决问题思路是某行记录被锁, 这行记录的主键距离上一个主键这个间隙里不准插入新增主键,这样来实现对幻影数据的杜绝

不可重复读: 

写数据时加上排他锁, 读数据时加上共享锁。



ACID靠什么保证的?

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

C一致性由其他三大特性保证、程序代码要保证业务上的一致性
I隔离性由MVCC来保证

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

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

什么是MVCC

多版本并发控制:

读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了, 不同的事务session会看到自己特定版本的数据,版本链

MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和 MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据 行。而 SERIALIZABLE 则会对所有读取的行都加锁。

聚簇索引记录中有两个必要的隐藏列:

trx_id:用来存储每次对某条聚簇索引记录进行修改的时候的事务id。

roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个 roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个 版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

已提交读和可重复读的区别就在于它们生成ReadView的策略不同。

   开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组 访问数据,获取数据中的事务id(获取的是事务id最大的记录),对比readview:

  如果在readview的左边(比readview都小),可以访问(在左边意味着该事务已经提交) 如果在readview的右边(比readview都大)或者就在readview中,不可以访问,获取roll_pointer,取 上一版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还 未提交)

  已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在 第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。

  这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同 实现不同的隔离级别。

 sql语句的执行流程:

 spring事务

@Transactional源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

Spring事务的实现方式和原理以及隔离级别?

  在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的, @Transactional注解就是申明式的。

  首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能 让程序员更加方便操作事务的方式。

  比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都 会在一个事务中执行,统一成功或失败。

  在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象 作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻 辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有 出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务 进行回滚。

  当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行 配置,默认情况下会对RuntimeException和Error进行回滚。

spring事务传播机制

  多个事务方法相互调用时,事务如何在这些方法间传播。

  方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都 会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就 由两个方法所定义的事务传播类型所决定。

REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事 务,则加入这个事务(就是一个事务,a事务,b事务 变为一个事务,一起成功一起失败)

SUPPORTS:当前(a)存在事务,(b)则加入当前事务,如果当前没有事务,就以非事务方法执行

MANDATORY:当前(a)存在事务,(b)则加入当前事务,如果当前事务不存在,(b)则抛出异常。

REQUIRES_NEW:(b)创建一个新事务,如果存在当前(a)事务,则挂起(a)该事务(各搞各的,两个事务)。

NOT_SUPPORTED:(b)以非事务方式执行,如果当前存在事务,则挂起当前(a)事务

NEVER:(b)不使用事务,如果当前事务存在,则(b)抛出异常

NESTED:如果当前事务存在,则在嵌套事务(a是父事务,b是子事务,a失败,b也失败;a成功b未必也失败)中执行,否则REQUIRED的操作一样(开启一个事务)

NESTED和REQUIRES_NEW的区别

REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我
们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。 在NESTED情况下父事务回滚时,
子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。

NESTED和REQUIRED的区别
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于 共用一个事务,所以无论调用方是否catch其异常,事务都会回滚 而在NESTED情况下,被调用方发生异常 时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响

spring事务什么时候会失效?

spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有 如下几种

1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而 是UserService对象本身!

  解决方法很简单,让那个this变成UserService的代理类即可!

2、方法不是public的

  @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可 以开启 AspectJ 代理模式。

3、数据库不支持事务

4、没有被spring管理

5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

个人学习笔记,记录日常学习,便于查阅及加深,仅为方便个人使用。
原文地址:https://www.cnblogs.com/wq-9/p/15168400.html