总览:
事务的4大特性(ACID)
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
事务并发访问的问题(隔离性)
脏读
两个事务正在并发的执行,事实上最后结果应该是1500才对,时间5时刻的查询余额为0就是脏数据,事务A读取了事务B中未提交的数据,这就是脏读。
时间
事务A
事务B
1
开始事务
-
2
-
开始事务
3
-
查询余额有1000
4
-
取出1000,余额0
5
查询余额0
-
6
-
撤销掉事务
7
存入500,余额500
-
8
提交事务
-
不可重复读
两个事务正在并发的执行,结果A两次读取的结果不一样,这是因为两次查询有间隔,期间被其他事务修改并提交了事务,相比脏读的区别是,不可重复读是读取另一事务提交的数据。这种现象也是正常的,是由于事务的隔离级造成的,但是在在某些特别的情况下也是不允许的。
时间
事务A
事务B
1
开始事务
-
2
-
开始事务
3
-
查询余额有1000
4
查询余额1000
-
5
取出1000,余额0
6
-
提交事务
7
查询余额0
-
幻读
两个事务正在并发的执行,事务A第一次统计和第二统计的结果不一样,是因为事务B新增了一条数据,和不可重复读一样,都是读取了另外一个事务的数据,不同的是不可重复读查询的是同一条数据,而幻读则是针对批量的数据,或者说不可重复读是A读取了B的更新数据,幻读是A读取了B的新增数据。
时间
事务A
事务B
1
开始事务
-
2
-
开始事务
3
统计总金额10000
-
4
-
-
5
存入100
6
-
提交事务
7
统计总金额10100
-
数据库的隔离级别(MySQL为例)
明白上面的问题之后就明白为什么需要隔离级别了,不同的隔离级别能处理不同的并发事务问题,下表:
事务级别
脏读
不可重复读
幻觉读
READ_UNCOMMITTED
允许
允许
允许
READ_COMMITTED
禁止
允许
允许
REPEATABLE_READ
禁止
禁止
允许
SERIALIZABLE
禁止
禁止
禁止
MySQL默认的事务级别是REPEATABLE_READ
JDBC的数据隔离级别设置
JDBC
访问
TRANSACTION_READ_UNCOMMITTED
ur
就是俗称“脏读”(dirty read),在没有提交数据时能够读到已经更新的数据
TRANSACTION_READ_COMMITTED
cs
在一个事务中进行查询时,允许读取提交前的数据,数据提交后,当前查询就可以读取到数据。update数据时候并不锁住表
TRANSACTION_REPEATABLE_READ
rs
在一个事务中进行查询时,不允许读取其他事务update的数据,允许读取到其他事务提交的新增数据
TRANSACTION_SERIALIZABLE
rr
在一个事务中进行查询时,不允许任何对这个查询表的数据修改。
Spring的事务传播行为
事务传播
事务怎么传播?方法A传播到方法B。
Spring解决的就是方法之间的事务传播。
下面看下每一种行为具体代表的含义:
REQUIRED
业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么这个时候就会加入到该事务中,如果当前没有事务环境的话,就会为自己创建一个新的事务。
SUPPORTS
这一事务属性表明,如果业务方法A在某个事务范围内被调用,则方法成为事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。即当标注了事务传播属性——SUPPORTS的业务方法在另一个bean的业务方法中执行时,如果另一个bean的业务方法开启了事务,它就会处在事务中执行,如果另一个bean的业务方法也没开启事务,那么它也在没有事务的环境中进行。
MANDATORY
该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出异常。一种比较强硬的方式。
REQUIRES_NEW
该属性表明不管当前是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
NOT_SUPPORTED
声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用(在其他业务bean的方法中被调用了,而其他业务bean的方法是开启了事务的),该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
NEVER
指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行。比较强硬的方式,就是不支持事务。
NESTED
(嵌套事务)如果一个活动的事务存在,则当前方法运行在一个嵌套的事务中。 如果没有活动事务,就创建一个新的事务。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。外部事务回滚会导致内部事务的回滚。如果被调用的内部方法没有捕获异常,跑出异常也会导致外部事务的回滚。
看下Spring中枚举定义的7种事务传播行为
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
在使用注解方式的事务时候我们可以用下面的方式来设置事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
是不是很方便。
只读事务
readOnly属性:设置为只读事务,对于只读事务,它就不能进行更新操作,一般只存在数据读取的时候,可以将readOnly属性设置为true,可提供效率。
事务超时
timeout属性:代表事务的超时时间,默认为30s,一般情况下都不需要设置超时时间。如果超过时间就回滚。