对于Transactional注解的使用这里不过多介绍,这里主要说一下Transactional中的嵌套事务,首先说明,**Mysql是不支持嵌套事务的。**但是Transactional中实现了嵌套事务。
首先说一下 基础知识
mysql事务的隔离级别有四种,分别是:未提交读、提交读、可重复读、串行化。
再解释一下四个术语:脏读、不可重复读、幻读、加锁读。
脏读: 就是A事务修改了一个字段信息,但是还没有提交,这个时候B事务来读取这个字段信息,读到的是A事务修改后的,如果此时,A事务回滚了,那么B事务之前读到的这个信息就是脏读。
不可重复读: 就是同一个事务里面,读取同一个字段信息,这一刻读到的和下一刻读到的不一样。就比如上面的例子,B事务一开始读到了A事务修改后的信息,然后A回滚,B再次读这个字段,那么信息就和之前不一样,这就是不可重复读。
幻读: 这个和脏读有点类似,就是A事务插入了一些数据,但是还没提交,然后B事务读取了这些数据,然后A事务发生了回滚,B事务再次读取,发现和刚刚读取的不一样了,就和出现了幻觉一样,所以就叫幻读。和脏读的区别在于,脏读关注的是更新,幻读关注的是插入。
加锁读: 就是指事务会将读取到的信息锁起来,就是在读或者进行其它操作的时候,不允许其它事务进行修改。
下面是不同隔离级别可能会出现哪些情况对应的表:
简单解释一下:
未提交读:
它可能出现脏读,就是说,在未提交读隔离级别下的事务A,可以读到所有隔离级别下的事务修改但是并没有提交的数据。那么A事务就发生了脏读。简单说就是一个事务读到了另一个事务还未提交的数据。
开启第一个事务,但没提交。
开启第二个事务,但是可以看到,这里已经读到了第一个事务没有提交的信息,这就是脏读。
他可能出现不可重复读,就是说,在第二个事务中,读取两次同样的字段,信息可能不一样。当信息不一样的时候,就发生了不可重复读。
接着上面的步骤,这里看仔细,我在第一个事务中执行一下回滚。
在第二个事务中在执行一个select,可以看到,和第一次执行得到的结果不一样了,这就是不可重复读。
它可能出现幻读,就是说在事务A中,第一次读取前,事务B插入了数据,还没提交,事务A查到了B事务插入得,然后B回滚,A在查,此时A事务就发生了幻读。
在第一个事务中执行插入。此时未提交事务。
可以看到第二个事务中已经看到了第一个事务增加的数据。(此时第一个事务还没有提交)
回滚第一个事务。
这里可以看到,在第二个事务中,第二次查看信息,发现,少了一行,这就是幻读。就和幻觉一样。
提交读:
这里第一个事务可以是任意的隔离级别。
这里要注意,这个事务要是提交读隔离级别,因为我们现在验证的就是提交读。
这里可以看到,此时,它就读不到第一个事务没有提交的结果了。
第一个事务提交。
再次执行第二个事务,这里可以看到,第二个事务看到了第一个事务提交后的结果,并且可以看出,第二个事务中,第一次select和第二次select得到了不一样的结果,这也就是不可重复读。
幻读这里就不贴了,就是第一个事务update那一行改为 insert,然后提交,第二个事务再次select会发现多了一行,这就是幻读,可以自行实验一下哦。
它会出现不可重复读,就是事务A,查询数据,然后事务B修改数据,但未提交(此时事务A是看不到事务B未提交的数据的),然后B提交了事务,此时事务A又一次查询刚刚的数据,发现不一样了,此时就发生了不可重复读。
它会出现幻读,就是事务A,查询数据,然后事务B插入了数据,然后提交,事务A再次查询数据,发现不一样了,此时就发生了幻读。
下面的可重复读和串行化可以自行实验一下哦,这里就不贴图了。
可重复读:(看名字,就知道,着肯定不会发生不可重复读)
它会发生幻读,和提交读发生幻读是一样的情况。
这里解释一下这种隔离级别是怎么解决的不可重复读问题,这里其实是要实现MVCC(多版本并发控制),说白了就是快照,就是说,在开启可重复读事务的时候,保存了数据的快照,也就是说,不管这个事务执行多长时间,它读到的数据都是一致的,因为这个事务操作的只是自己的快照而已,但是这里为什么会出现幻读呢,因为 可重复读隔离级别下,如果事务进行了update insert delete,就会更新快照。这里有点难理解,我举个例子吧。
可重复读隔离级别下,事务A执行了select,那么就生成了一个快照,其它事务(可以是不同隔离级别的)随便执行,随便提交,都不会影响事务A的select操作,所以就解决了 不可重复读,但是只要事务 A执行了update insert delete中的任何一个操作,然后再执行select,就会读到其它事务插入的数据,由此会出现幻读的可能性。这里有个注意的地方,就是当其它事务删除了原有的数据后,并且提交后,事务A再执行select后,快照里面原有的数据依然会存在,不会因为其它事务删除了就没有了,因为可重复读隔离级别下,只要快照备份了,快照上的数据就不会被修改和删除,但是可以因其它事务的insert而增加。
串行化: 这是最高隔离级别,它是强制事务串行化执行的,简单说就是,该隔离级别,在读取的每一行上加上了 锁(这里可能是读锁也可能是写锁)。所以可以避免幻读、脏读、不可重读的问题。但是这可能导致大量的超时和锁争用的问题。所以除非非常需要保持数据一致性而且可以接受没有并发的情况才会考虑这种隔离级别。说白了就是,这种隔离级别,没有并发。
这里再简单解释一下读锁和写锁吧,免得小伙伴留有疑虑:
读锁: 读锁也叫共享锁,一个事务给某些数据添加了读锁后,其它事务也可以继续给这些数据添加读锁,并且也可以读到这些数据,但是不能修改或删除这些数据。修改删除会进入阻塞状态,直到其它事务释放读锁。
写锁: 写锁也叫排他锁,一个事务给某些数据添加了写锁后,其它事务不能再给这些数据添加锁(读写锁都不行)。但是还是可以读到这些数据。(这里的读是指不添加锁的读取)
如果想在事务中再开一个事务,在Mysql中是不行的,是不是有人想这样呢:
BEGIN; UPDATE article SET title = "测试02" WHERE id =1; BEGIN;#此时第一个事务会隐式提交,无法实现嵌套事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; ROLLBACK; COMMIT;
但是可以通过安全点来实现类似的做法:
BEGIN; UPDATE article SET title = "测试02" WHERE id =1; SAVEPOINT p1;#设置安全点,也可以说是回滚点 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; ROLLBACK TO p1; COMMIT;
这样的做法就是都是相同隔离级别下。
现在开始说一下 这个注解实现嵌套事务,它是怎么做到实现的嵌套事务的呢,其实他会在开启嵌套事务的时候,将第一个事务挂起。这里需要注意的是,第二个事务是基于哪个表建立的,这个一定要弄明白。,也就是说,第一个事务修改了数据,但是未提交,此时开启第二个事务,第二个事务查出来的结果会是什么样子,这里感兴趣的同学可以自己做一下实验,实际操作一下会清晰很多,这里的结论其实就是上面的表:(这里不懂得同学,自己一定要手动写代码测试一下嵌套事务会是什么结果!!!)
事务传播级别,还有开启嵌套事务有个地方要注意,就是:
因为直接调用this.function(),这样调用是不会开启事务的,要通过自动注入的对象,调用代理后的方法,这样才会开启新的的事务。(当然,这里还要搭配好事务的传播属性来使用。)
这里解释一下事务传播各种值代表的意思:
下面贴出源码:
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; } }
REQUIRED: 如果存在事务,使用当前的事务,如果当前没有事务,就自己新建一个事务,就是说如果调用这个方法就是在之前的一个事务中调用的,那么就加入这个事务。
SUPPORTS: 如果存在事务,就使用事务,如果没有事务,就不使用。(这个单词是支持,就是支持事务,也就是说你有事务,我就继续使用这个事务,如果没有,我就不用,仅仅是支持而已,自己不会去开启一个事务)
MANDATORY: 调用加了这个值的方法,必须已经开好了事务,不然就报错。
REQUIRES_NEW: 如果当前有事务,就挂起之前的事务,自己建立一个新的事务,如果当前没有事务,就还是自己建立一个事务
NOT_SUPPORTED: 调用者不能用任何的事务,就是开启了事务,不能在事务中调用这个值的方法。
NEVER: 这个和MANDATORY相反,必须没有事务,不然就报错。
NESTED: 调用者发生了异常,嵌套的事务也会受到影响,发生回滚。嵌套的事务发生异常,父事务也会发生回滚。即:嵌套的事务,任何一个发生异常,所有事务都发生回滚。但是调用方可以加上try catch,如果异常被捕获了,父事务就可以不进行回滚,可以继续进行自己的操作。