事务简介
有时候一个业务需要多次操作数据库,比如转账
如果reduce()执行成功,add执行失败,那钱是转出去了,但对方并没有收到,钱转丢了,应该加事务。
事务:逻辑上的一组操作,要么全部成功,要么全部失败。
事务的4个特性
- 原子性(A):事务是执行数据库操作的最小单元(默认一个sql语句就是一个事务),一个事务中的操作,要么全部成功,要么全部失败
- 一致性(C):事务执行前后,数据库中的数据应该保持一致。比如说A转账给B,转账前两人账户加起来一共有5W元,转账后两人账户加起来也应该是5W元。
- 隔离性(I):事务之间应该是隔离的,一个事务的执行不应该受到其它事务的干扰、影响
- 持久性(D):事务一旦提交,便会将修改持久化到数据库
事务的并发执行
如果没实现隔离性,事务并发执行时可能会出现以下问题
- 读问题:脏读、不可重复读、虚读(幻读)
- 写问题:丢失更新
示例 B取款的同时,A转账给B
虽然2个事务是并发执行的,但实际执行具体语句时仍有先后顺序。
脏读
一个事务读取到另一个事务未提交的数据。
eg. 事务A读取到事务B未提交的数据,事务B执行失败回滚,事务A之前读到的数据变成无效数据,但事务A并不知道这是无效数据,用这些数据来操作。
隔离级别 Read commited 已提交读,可解决脏读问题,等到使用这些记录的事务提交后才读取数据。
不可重复读
前后多次读取,读取到的数据内容不一致(期间已被其它事务使用update修改)。
eg. 事务A持续时间长,需要多次从数据库读取数据,但期间其它事务对数据库中的这部分数据进行了修改,事务A前后读取的数据对不上,前后执行的操作也就有误。
隔离级别 Repeatable read 重复读,可解决不可重复读的问题,当有事务读了某些行的数据后,这些行会被锁住,不允许其它事务对这些行进行修改,这样重复读取到的数据就是一致的。
因为使用某些行时,这些行会被锁定,其它事务不能读取这些行,也避免了脏读问题。
虚读(幻读)
前后多次读取,读取到的记录数不一致(期间其它事务使用insert、delete造成记录数变化)。
隔离级别 Serializable 可解决幻读问题,不允许事务并发,最安全,但性能最差,基本不用。
丢失更新
丢失更新是不可重复读中的一种特殊情况,2个事务都要修改记录内容(update),后提交的覆盖了前面提交的
事务的隔离级别
可通过设置事务的隔离级别,来解决读问题。
事务的4种隔离级别
- Read uncommitted:未提交读,解决不了任何读问题,安全性最低,但事务执行效率最高
- Read committed:已提交读,解决了脏读,但不可重复读、虚读有可能发生
- Repeatable read:重复度,解决了脏读、不可重复读,但虚读有可能发生
- Serializable:不允许事务并发,安全性最高,但事务执行效率最低
一般折中使用第2、3项,mysql默认用Repeatable read,oracle默认用Read committed,使用默认的即可。
数据库自身提供了4种事务隔离级别,spring还提供一种:
- DEFAULT 使用数据库本身默认的隔离级别
事务的传播行为
事务管理是添加在业务层的,如果service层的方法发生相互调用
public void a(){
//....
b();
//....
}
被调b()可能添加了事务,如果要给主调a()添加事务,如何添加?
事务的传播行为:service层发生方法的相互调用时,如何处理给主调方法加的事务
spring中定义了7种事务传播行为,默认使用REQUIRED,这个也是最常用的。
使用事务
事务是针对多个数据库操作的(dao层),dao层在service层调用,所以事务要加到service层的方法上。
mybatis的依赖中已经包含了事务管理,不需要额外导包,要对service层的某些业务方法使用事务,直接在方法上标注 @Transactional 即可。