1、事务的四大特性
- Atomic:原子性,就是一堆SQL,要么一起成功,要么都别执行,不允许某个SQL成功了,某个SQL失败了,这就是扯淡,不是原子性。
- Consistency:一致性,这个是针对数据一致性来说的,就是一组SQL执行之前,数据必须是准确的,执行之后,数据也必须是准确的。别搞了半天,执行完了SQL,结果SQL对应的数据修改没给你执行,那不是坑爹么。
- Isolation:隔离性,这个就是说多个事务在跑的时候不能互相干扰,别事务A操作个数据,弄到一半儿还没弄好呢,结果事务B来改了这个数据,导致事务A的操作出错了,那不就搞笑了。
- Durability:持久性,事务成功了,就必须永久对数据的修改是有效的,别过了一会儿数据自己没了,不见了,那就好玩儿了。
2、事务隔离级别
- 读未提交:Read Uncommitted:这个很坑爹,就是说某个事务还没提交的时候,修改的数据,就让别的事务给读到了,这就恶心了,很容易导致出错的。这个也叫做脏读。(一个事务内读到另外一个事务还未提交的数据)
- 读已提交:Read Uncommitted:这个很坑爹,就是说某个事务还没提交的时候,修改的数据,就让别的事务给读到了,这就恶心了,很容易导致出错的。这个也叫做脏读。(一个事务内读到另外一个事务提交的数据)
- 可重复读:Read Repeatable:这个就是比上面那个再好点儿,就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B修改了数据的值还提交了,但是事务A读到的还是自己事务开始时这个数据的值。(同一个事务里面对于同一个查询结果一致)
- 串行化:幻读,不可重复读和可重复读都是针对两个事务同时对某条数据在修改,但是幻读针对的是插入,比如某个事务把所有行的某个字段都修改为了2,结果另外一个事务插入了一条数据,那个字段的值是1,然后就尴尬了。第一个事务会突然发现多出来一条数据,那个数据的字段是1。如果要解决幻读,就需要使用串行化级别的隔离级别,所有事务都串行起来,不允许多个事务并行操作。
MySQL的默认隔离级别是Read Repeatable,就是可重复读,就是说每个事务都会开启一个自己要操作的某个数据的快照,事务期间,读到的都是这个数据的快照罢了,对一个数据的多次读都是一样的。
3、Spring支持的事务支持
spring支持编程式事务,和声明式事务。编程式事务就是用个事务类TransactionTemplate来管理事务,这个一般现在没人傻到干这个事儿了;声明式事务分成在xml里配置个AOP来声明个切面加事务,一般现在也没人傻到干这个了;大部分情况下,都是用@Transactional注解。
另外这个注解一般要加rollbackFor,就是指定哪些异常类型才要回滚事务
还有比较重要的,就是有个isolation属性,你可以自己手动调整事务的隔离级别,但是这个一般不调整,记住,别乱调整事务隔离级别,一般可重复读+mysql mvcc机制跑的很好,你别瞎折腾。
Spring的事务传播机制
另外一个重要的事务属性,就是propagation,事务的传播行为,我们就重点先来聊一下事务的传播行为,@Transactional的事务方法,和嵌套了另外一个@Transactional的事务方法的时候,包括再次嵌入@Transactional事务方法的时候,这个事务怎么玩儿?
7种事务传播行为
- PROPAGATION_REQUIRED:这个是最常见的,就是说,如果ServiceA.method调用了ServiceB.method,如果ServiceA.method开启了事务,然后ServiceB.method也声明了事务,那么ServiceB.method不会开启独立事务,而是将自己的操作放在ServiceA.method的事务中来执行,ServiceA和ServiceB任何一个报错都会导致整个事务回滚。这就是默认的行为,其实一般我们都是需要这样子的。
- PROPAGATION_SUPPORTS:如果ServiceA.method开了事务,那么ServiceB就将自己加入ServiceA中来运行,如果ServiceA.method没有开事务,那么ServiceB自己也不开事务
- PROPAGATION_MANDATORY:必须被一个开启了事务的方法来调用自己,否则报错
- PROPAGATION_REQUIRES_NEW:ServiceB.method强制性自己开启一个新的事务,然后ServiceA.method的事务会卡住,等ServiceB事务完了自己再继续。这就是影响的回滚了,如果ServiceA报错了,ServiceB是不会受到影响的,ServiceB报错了,ServiceA也可以选择性的回滚或者是提交。
- PROPAGATION_NOT_SUPPORTED:就是ServiceB.method不支持事务,ServiceA的事务执行到ServiceB那儿,就挂起来了,ServiceB用非事务方式运行结束,ServiceA事务再继续运行。这个好处就是ServiceB代码报错不会让ServiceA回滚。
- PROPAGATION_NEVER:不能被一个事务来调用,ServiceA.method开事务了,但是调用了ServiceB会报错
- PROPAGATION_NESTED:开启嵌套事务,ServiceB开启一个子事务,如果回滚的话,那么ServiceB就回滚到开启子事务的这个save point。
其实一般也就PROPAGATION_REQUIRES_NEW比较常用,要的效果就是嵌套的那个事务是独立的事务,自己提交或者回滚,不影响外面的大事务,外面的大事务可以获取抛出的异常,自己决定是继续提交大事务还是回滚大事务。
一般在单块系统开发,多人协作的时候比较常见,就是小A调用小B的模块,小A不管小B是成功还是不成功,自己都要提交,这个时候可以这么弄,就是说小B的操作不是构成小A的事务的重要组成部分,就是个分支。