事务隔离级别 事务的传播属性 脏读 重复读 幻读
仅此一文让你明白事务隔离级别、事务的传播属性、脏读、不可重复读、幻读
脏读 重复读 幻读
事务的基本要素(ACID)- 原子性(Atomicity):一个事务必须操作完成,不会停滞在中间环节,执行过程出错,回滚到事务开始前的状态。该事务是一个不可分割的整体-如化学中学过的原子,是物质构成的基本单位。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
- 隔离性(Isolation):同一时刻,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
- 持久性(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>
而现在有一种简单的方法,如下
- 首先在spring-mybaits.xml中开启事务注解驱动
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> <!--dataSourceTransactionManager还是之前配置的--> <!--事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> <property name="enforceReadOnly" value="false"/> </bean>
- 开启了事务注解驱动之后,只需要在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<? extends java.lang.Throwable>[] rollbackFor() default {}; java.lang.String[] rollbackForClassName() default {}; java.lang.Class<? extends java.lang.Throwable>[] noRollbackFor() default {}; java.lang.String[] noRollbackForClassName() default {};
}
具体代码就如下了@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)