事务隔离级别 事务的传播属性 脏读 重复读 幻读-编程思维

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

脏读 重复读 幻读
事务的基本要素(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)
  • 不可重复读(Norepeatable Read)
    • B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样。B事务这种读取的结果,即为不可重复读(Nonrepeatable Read
    • 不可重复读有一种特殊情况,两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失。这种情况就是大名鼎鼎的第二类丢失更新。主流的数据库已经默认屏蔽了第一类丢失更新问题(即:后做的事务撤销,发生回滚造成已完成事务的更新丢失),但我们编程的时候仍需要特别注意第二类丢失更新
  • 幻读(Phantom Read)
    • B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样
数据库隔离级别(Isolation)
  • 读未提交(Read Uncommitted)
    • 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题
  • 读已提交(Read Committed)
    • 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题
  • 可重复读(Repeatable Read)
    • 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题
  • 可串行化(Serializable)
    • 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁
  • 以上四种隔离级别会产生如下问题
隔离级别 含义
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)
    
 

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/antake/p/12763469.html

个人文章-编程思维

事务作为传统关系型数据库支持的重要特性,诚然一部分NoSQL数据库为了在分布式场景下可用性和性能的考虑都不在支持事务,但是代表未来趋势的NewSQL数据库在分布式场景下依然保留事务并支持ACID特性,这说明事务在数据库实现中依然是一个重要的功能特性。作为简化业务开发的重要模型工具,事务依然值得我们深入的研究、借鉴。事务

java多线程中如何回滚数据?-编程思维

背景介绍   1,最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚。 2,在spring中可以使用@Transactional注解去控制事务,使出现异常时会进行回滚,在多线程中

个人文章-编程思维

场景描述:统一登录平台之前的操作步骤(用户应用绑定,用户角色绑定)比较多,为提升用户体验,统一批量操作,后来就加了一个用户组的概念(用户绑定用户组和角色,用户组绑定多个角色,角色绑定多个应用,这里涉及到一些交并集操作),但是随之而来带来了一个问题,因为该系统其实主要是面对B端用户,并发其实不高,很难遇到一些并发导致的问

分布式事务解决方案-编程思维

数据不会无缘无故丢失,也不会莫名其妙增加 一、概述 1、曾几何时,知了在一家小公司做项目的时候,都是一个服务打天下,所以涉及到数据一致性的问题,都是直接用本地事务处理。 2、随着时间的推移,用户量增大了,发现一个Java服务扛不住了,于是技术大佬决定对于系统进行升级。根据系统的业务对于单体的一个服务进行拆分,然后对

sql server死锁产生原因及解决办法 .-编程思维

其实所有的死锁最深层的原因就是一个:资源竞争   表现一:   一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了,同样用户B要等用户A释放表A才能继续这就死

云图说丨云数据库gaussdb(for mysql)事务拆分大揭秘-编程思维

摘要:数据库代理提供事务拆分的功能,能够将事务内写操作之前的读请求转发到只读节点,降低主节点负载。 本文分享自华为云社区《【云图说】第270期 云数据库GaussDB(for MySQL)事务拆分大揭秘》,作者:阅识风云。 默认情况下,云数据库GaussDB(for MySQL)数据库代理会将事务内的所有请求都发送到主