事务隔离级别 事务的传播属性 脏读 重复读 幻读

仅此一文让你明白事务隔离级别、事务的传播属性、脏读、不可重复读、幻读

脏读 重复读 幻读
事务的基本要素(ACID)
  1. 原子性(Atomicity):一个事务必须操作完成,不会停滞在中间环节,执行过程出错,回滚到事务开始前的状态。该事务是一个不可分割的整体-如化学中学过的原子,是物质构成的基本单位。
  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  3. 隔离性(Isolation):同一时刻,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
事务的并发问题

事务是现代关系型数据库的核心之一。在多个事务并发操作数据库(多线程、网络并发等)的时候,如果没有有效的避免机制,就会出现以下几种问题:

  • 第一类丢失更新(Lost Update)
    • 在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生,就不在这里占用篇幅,有兴趣的可以自行百度
  • 脏读(Dirty Read)
    • A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因,A事务可能没有完成提交,发生RollBack了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)
    • 微信截图_20190223000611
  • 不可重复读(Norepeatable Read)
    • 微信截图_20190223000611
    • B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样。B事务这种读取的结果,即为不可重复读(Nonrepeatable Read
    • 不可重复读有一种特殊情况,两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失。这种情况就是大名鼎鼎的第二类丢失更新。主流的数据库已经默认屏蔽了第一类丢失更新问题(即:后做的事务撤销,发生回滚造成已完成事务的更新丢失),但我们编程的时候仍需要特别注意第二类丢失更新
  • 幻读(Phantom Read)
    • B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样
    • 微信截图_20190223102400
数据库隔离级别(Isolation)
  • 读未提交(Read Uncommitted)
    • 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题
  • 读已提交(Read Committed)
    • 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题
  • 可重复读(Repeatable Read)
    • 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题
  • 可串行化(Serializable)
    • 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁
  • 以上四种隔离级别会产生如下问题
    • 微信截图_20190223102400
隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
ISOLATION_READ_COMMITTED 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。
ISOLATION_SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。
事务的传播属性(Propagation)
Propagationkey属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为

事务的第一个方面是传播行为。传播行为定义关于客户端和被调用方法的事务边界。Spring定义了7中传播行为。

传播行为 意义
PROPAGATION_REQUIRED 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。
PROPAGATION_SUPPORTS 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。
PROPAGATION_MANDATORY 表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常。
PROPAGATION_REQUIRES_NEW 表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。
PROPAGATION_NOT_SUPPORTED 以非事务的方式运行,如果当前有事务,就将当前事务挂起。
PROPAGATION_NEVER 表示当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。
PROPAGATION_NESTED 表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像PROPAGATION_REQUIRES一样。
传播规则回答了这样一个问题,就是一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
let's do it
在之前的一文 SSM整合中我们提到了开启事务处理,spring-mybatis.xml代码如下
<!--提供事务需要管理的方法-->
    <tx:advice id="transactionInterceptor" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="query*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="update*" propagation="REQUIRED" isolation="REPEATABLE_READ" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" propagation="REQUIRED" isolation="REPEATABLE_READ" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.antake.ssm.service..*Impl.*(..))"/>
        <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="pc"/>
    </aop:config>
而现在有一种简单的方法,如下
  1. 首先在spring-mybaits.xml中开启事务注解驱动
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    <!--dataSourceTransactionManager还是之前配置的-->
    <!--事务管理器-->
    
    &lt;bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt;
        &lt;property name="dataSource" ref="dataSource"/&gt;
        &lt;property name="enforceReadOnly" value="false"/&gt;
    &lt;/bean&gt;
    

  2. 开启了事务注解驱动之后,只需要在serviceImpl方法上使用注解@Transactional就可以了
    //@Transactional.class
    @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD})
    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
    @java.lang.annotation.Inherited
    @java.lang.annotation.Documented
    public @interface Transactional {
        @org.springframework.core.annotation.AliasFor("transactionManager")
        java.lang.String value() default "";
    
    @org.springframework.core.annotation.AliasFor("value")
    java.lang.String transactionManager() default "";
    
    org.springframework.transaction.annotation.Propagation propagation() default org.springframework.transaction.annotation.Propagation.REQUIRED;
    
    org.springframework.transaction.annotation.Isolation isolation() default org.springframework.transaction.annotation.Isolation.DEFAULT;
    
    int timeout() default -1;
    
    boolean readOnly() default false;
    
    java.lang.Class&lt;? extends java.lang.Throwable&gt;[] rollbackFor() default {};
    
    java.lang.String[] rollbackForClassName() default {};
    
    java.lang.Class&lt;? extends java.lang.Throwable&gt;[] noRollbackFor() default {};
    
    java.lang.String[] noRollbackForClassName() default {};
    

    }


    具体代码就如下了

    @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
    
 
posted @ 2020-04-24 14:17  Antake  阅读(334)  评论(0)    收藏  举报
Live2D