事务总结
有时候一个业务需要多次操作数据库,比如转账:
如果reduce()执行成功,add执行失败,那钱是转出去了,但对方并没有收到,钱转丢了。
使用事务可解决此问题。
事务:逻辑上的一组操作,要么全部成功,要么全部失败。
事务的4个特性
- 原子性:最小单元,不可再分割
- 一致性:事务执行前后,数据的完整性保持一致。比如说A转账给B,转账前两人账户加起来一共有5W元,转账后两人账户加起来也应该是5W元。
- 隔离性:一个事务的执行不应受到其他事务的干扰
- 持久性:一旦事务结束(包括成功、失败回滚),数据就持久化到数据库
如果没做到隔离性,可能会引发读写问题
读问题:
- 脏读
- 不可重复读
- 虚读(幻读)
写问题:
- 丢失更新
脏读 读取另一个事务未提交的数据
原本数据库中的数据是正确的、干净的,
B事务更改了数据库中的数据,
A事务读取数据库中的数据(被事务B修改后的数据),
事务B发生错误,将数据库中的数据回滚成原本干净的数据,A读取到的数据变成了无效数据、脏数据。
但事务A什么都不知道,使用之前读取到的数据(脏数据)做一些操作。
示例 B取款的同时,A转账给B
虽然2个事务是并发执行的,但实际上,执行具体代码时仍有先后顺序。
不可重复读 前后多次读取,读取到的数据内容不一致(期间已被其它事务修改)
事务A比较大,前后需要多次从数据库读取同一段数据。
开始事务
前一次从数据库读取数据,赋给一些变量来使用,
使用这些变量来做一些操作,
随着代码的执行,这些变量逐渐被销毁(方法调用结束,局部变量被销毁;对象长时间闲置,被gc回收等),
之后需要再次使用这些数据,因为变量已被销毁,只有重新从数据库读取数据(执行的仍是之前的sql语句)来使用,
但这2次读取期间,有其它的事务修改了数据库中的这些数据的内容,
2次读取到的数据不一致(不是重复的),称为不可重复读。
但事务A不知道前后的数据不一致,仍然使用第二次读取的数据做一些操作。
提交事务。
第一次读取的数据已经无效了,使用这些数据做的操作无效,即前面部分做的操作与后面部分做的操作对不上。
虚读(幻读) 前后多次读取,读取到的记录数不一致(期间已被其它事务修改)
虚读和不可重复读差不多,都是前后读取相同的内容(执行一样的sql查询),
不可重复读是记录数没变化、但记录的内容被其它事务修改了(update xxx_tb set xxx=xxx),
虚读是读取到的记录数变了(其它事务使用insert、delete修改了数据库中读取的部分,导致记录总数不一致)。
可通过设置事务的隔离级别,来解决读问题。
事务的4种隔离级别
- Read uncommitted:未提交读,解决不了任何读问题,安全性最低,但事务执行效率最高
- Read committed:已提交读,解决了脏读,但不可重复读、虚读有可能发生
- Repeatable read:重复度,解决了脏读、不可重复读,但虚读有可能发生
- Serializable:不允许事务并发,安全性最高,但事务执行效率最低
一般是折中使用第2、3项,mysql用Repeatable read,oracle用Read committed。
事务的传播行为
事务管理是添加在业务层的,给Service中的方法添加事务管理。
一般情况下:
- 在Service中调用某个Dao的1个或多个方法,比如上图的Service1
- 在Service中调用1个或多个Dao的方法,比如上图的Service2
就可以完成业务。
但有时候业务特别复杂,需要调用业务层的其它方法:
包括调用其它Service的方法、本Service的其它的方法。
调用的方法可能添加了事务,对于Service.x()带过来的事务(被调方法传播过来的事务),该如何处理?
事务的传播行为就是:在业务层的方法相互调用时,规定主调方法定义自身的事务管理(是否使用被调方法的事务)。
Spring中的7种事务传播行为
以下面的调用为例:
大致可分为3类:(红色标示的项是常见的,只需记住红色标示的项)
(1)保证多个操作在同一个事务中
- PROPAGATION_REQUIRED 默认值,一般都是使用这个。
如果被调方法有事务,主调方法就使用被调方法的事务;如果被调方法没有事务,会创建一个新事务,管理主调方法。
会把主调方法的其它代码包含进来,一起管理。被调方法指的是业务层的其它其它方法Service1.x(),主调方法指的是z(),包括Service1.x() + Dao2.c() 。
required 要求、需要、必需的,对于主调方法,事务是必需的。
- PROPAGATION_SUPPORTS
如果被调方法有事务,主调方法就使用被调方法的事务;如果被调方法没有事务,主调方法就不使用事务。
supports,有就支持,没有就算了(都不使用事务)。
- PROPAGATION_MANDATORY
如果被调方法有事务,主调方法就使用被调方法的事务;如果被调方法没有事务,则抛出异常。
(2)保证多个操作不在同一个事务中
- PROPAGATION_REQUIRES_NEW
requires_new 对于主调方法,事务是必需的,且事务要是新建的(只管里主调方法的其它部分,被调方法的事务管理维持原状)。
会新建一个事务,管理主调方法(的其它部分),被调方法的事务管理维持原状。
- PROPAGATION_NOT_SUPPORTED
不管被调方法有没有事务,主调方法都不使用事务(其它部分不使用,被调方法的事务管理维持原状)。
not_supported,主调方法不支持事务。
- PROPAGATION_NEVER
如果被调方法有事务,直接抛出异常。
never,决不能有事务。
(3)嵌套式事务
- PROPAGATION_NESTED
在主调方法执行前、被调方法执行的前后设置还原点,失败时可以回滚到某个还原点。
执行主调方法之前先设置一个还原点,执行主调方法的其它代码(如果有)。
如果执行失败,回滚到之前的还原点;如果执行成功,再设置一个还原点,执行被调方法(如果被调方法有事务,会执行被调方法的事务)。
如果被调方法执行失败,回滚到之前的某个还原点(可设置),如果被调方法执行成功,设置一个还原点,执行后续的其它代码。
如果执行失败,回滚到之前的某个还原点(可设置)。