一:事务是什么
写代码10年了,平时涉及需要事务处理的代码块也就复制粘贴,虽然知道如何使用,但对原理一直也是似懂非懂,最近小组内部分享了spring事务初始化,说实话里面蛮多都不是很明白,于是私下花了点时间重新学习了一下。
那究竟什么是事务?为什么spring有事务机制呢?事务传播又是啥?
解释这些困惑之前,先举一个经典的例子:
银行转帐业务:账户A要把1000元转到B账 户,也就是A账户余额要减去1000元,然后B账户要增加1000元。假如在转账过程中出现了网络等问题,A账户减去1000元已成功,B因为网络中断而操作失败,那么本次转账业务应该失败,同步要求A账户转帐业务撤销,这才能保证业务的正确性。完成这个操作就需要事务,将A账户资金减少和B账户资金增加放到一个事务里面,要么全部执行成功,要么操作全部撤销,这样就保持了数据的安全性。
从上面例子我们明白下面2个疑惑:
1.为什么需要事务?
事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
2.什么是事务?
在一个将数据从一种状态改变到另一种状态的执行单元中,有多个操作,对我们来说一个执行单元是一个整体,我们希望在这个整体中所有操作要么都执行,要么都不执行,以便维护数据的一致性,这就是事务。
理解了事务概念之后,接下去我们了解一下事务4个特性(ACID):
原子性(atomicity):将事务中所做的操作捆绑成一个原子单元,即对于事务所进行的数据修改等操作,要么全部执行,要么全部不执行。
一致性(Consistency):表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。
隔离性(Isolation):表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。
永久性(Durability):事务完成之后,它对系统的影响是永久的,即使出现系统故障也是如此。
理清楚事务的四大特性(简称ACID)后,接下去来继续讲解事务的隔离性。
二:事务隔离
1.为什么要事务隔离?
当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。如果不考虑事务的隔离性,会发生的几种问题:
- 脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
举例:我写了一篇文章,保存在草稿中,还没正式发布,假如就在这个时候后台管理员有权限看到我的文章,此刻他看到的信息我们标识为A信息。但是我发现写错了,撤销了原先写的内容,重新变更了内容,然后确认提交发布,这个时候信息我们标识为B。
分析上面,实际发布的文章信息是B,但管理员看到的信息是A,也就是看到了我事务未提交的数据,这就是脏读。
- 不可重复读
也可理解为“读提交”,按字面意思就更好理解,就是:一个事务读取了前一事务提交的数据。
举例:还是接着上面的例子说明,当普通用户已经打开网页在看我的文章的时候,这个时候我又更改了部分内容然后提交,这个时候当普通用户再刷新网页的时候,就会发现同样这篇文章,内容不一样了。
分析上面,普通用户看的文章信息都是我事务提交的数据。
- 幻读
幻读是事务非独立执行时发生的一种现象。一个事务执行了两次查询,发现第二次查询结果比第一次查询多出了一行,这可能是因为另一个事务在这两次查询之间插入了新行。
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
2.数据4种隔离级别
数据隔离级别,定义的是事务在数据库端读写方面的控制范围。在读取数据库的过程中,如果两个事务并发执行,那么多个事务彼此之间,会对数据产生影响。网上有篇文章(http://blog.csdn.net/QQ994406030/article/details/79491131)例子非常好,这边转载过来。
Serializable:串行化。这是最严格的隔离级别,它要求所有事务都被串行执行,即事务只能一个接一个地进行处理,不能并发执行,资源消耗极大。
Repeatable Read:可重复读。该级别可以确保一个已经被事务读取的数据,另一个事务不能修改这个数据,从而避免了 “脏读” 和 “不可重复读”。仍然有较大的性能损耗。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
Read Commited:读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。这是大部分主流数据库默认的数据隔离级别。该级别下,只允许读已经提交的数据。例如:当一个事务修改了数据但未提交时,另一个并行事务只会读到该数据修改之前的内容,从而避免了 “脏读”。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
Read Uncommited:读未提交。一个事务可以读取另一个并行事务已修改但还未提交的数据。会产生 “脏读”。
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
三:事务传播
1.事务传播是什么?
当调用Service层的一个方法的时候,事务保证方法中所有有关数据库更新操作在一个原子中。在事务层里面调用的这些方法要么全部成功,要么全部失败。传播的属性包括是否使用事务、事务是否可以传播、是否可以嵌套事务等。它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。
2.spring 7种事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
这块网上一篇文章讲解很详细,直接转载过来。
spring事务传播详解:https://baijiahao.baidu.com/s?id=1593192556844228644&wfr=spider&for=pc
JDK动态代理给Spring事务埋下的坑:http://blog.csdn.net/bntx2jsqfehy7/article/details/79040349
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。
五:事务失效
- spring的事务传播策略在内部方法调用时将不起作用。这点务必注意!
- 当绕过代理对象, 直接调用添加事务管理的方法时, 事务管理将无法生效。
- private 方法无法添加事务管理。
- final 方法无法添加事务管理。
- static 方法无法添加事务管理。