问题初现
背景:java中方法a(加了数据库事务表明)调用了方法b(同样加了数据库事务表明),B中对表t的部分行实行了更新操作;方法a中在调用b后,实行了对表t的select操作,但创造select到的数据中并不包含B的修正。用大略单纯代码大概表示为:
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor=Exception.class)public void a() {
问题大概可以总结为,嵌套事务中实行的数据库修正操为难刁难外层事务不可见,首先预测是@Transactional表明中的事务传播属性 REQUIRES_NEW
的问题,会不会由于b方法新起了事务导致不同事务之间修正不可见?mysql事务隔离级别是默认的 Repeatable Read
。

事务传播属性
spring中统共定义了七种事务传播属性:
//public enum Propagation { //支持当前事务,如果当前没有事务,就新建一个事务,这是最常用的选择
个中 REQUIRES_NEW
和 NESTED
随意马虎稠浊,看起来都是在原有事务的根本上再开一个事务,他们的差异在哪?嵌套事务机制到底是若何,事务之间的提交和回滚分别是何时触发的?
REQUIRES_NEW
会启动一个新事务,这个事务将被完备 commited 或 rolled back 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始实行时,外部事务将被挂起,内务事务结束时,外部事务将连续实行。
NESTED
会开始一个 “嵌套的” 事务,它是已经存在事务的一个真正的子事务。 嵌套事务开始实行时,它将取得一个 savepoint
,如果这个嵌套事务失落败,将回滚到此 savepoint
。 嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。
由此可见, REQUIRES_NEW
启动的新事务不依赖于外部事务,是完备独立的,这意味着事务commit和rollback操作都是独立的,不受外部事务commit或者rollback影响。 NESTED
是依赖于外部事务的子事务,只有当外部事务commit时,子事务才能commit;外部事务发生非常rollback,子事务也要回滚。
回到上面的问题,方法b的事务传播属性设置为 REQUIRES_NEW
,意味着会开启一个完备独立的事务。当方法a中实行 selectFromTableT
方法时,方法b中对表操作已经提交,那方法a中事务内为什么不可见已经提交的变动?是时候捡起数据库隔离级别细读一番了。
mysql事务隔离级别
首先四种事务隔离级别:
1. Read Uncommitted(读取未提交内容)在该隔离级别,所有事务都可以看到其他未提交事务的实行结果。本隔离级别很少用于实际运用,由于它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
2. Read Committed(读取提交内容)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它知足了隔离的大略定义:一个事务只能瞥见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),由于同一事务的其他实例在该实例处理其间可能会有新的commit,以是同一select可能返回不同结果。
3. Repeatable Read(可重读)这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。大略的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会创造有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发掌握(MVCC,Multiversion Concurrency Control)机制办理了该问题。
4. Serializable(可串行化)这是最高的隔离级别,它通过逼迫事务排序,使之不可能相互冲突,从而办理幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时征象和锁竞争。
不同事务隔离级别下几种范例问题:
1. 脏读紧张表现为一个事务中前后两次读取数据不一致,例如在Read Uncommitted隔离级别下,一个事务可以读取其他未提交事务的修正,前后两次读取之间可能有其他事务的修正或者回滚操作,导致前后数据不一致;
2. 幻读紧张表现为一个事务前后读取行数不一致或者读到了不存在的数据,用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会创造有新的“幻影” 行。
3. 不可重复读和脏读类似,表现为一个事务内前后两次读取数据不一致,不可重复读问题是由于在一个事务实行过程中有其他事务已经commit的修正,导致前后读取不一致。
看起来以前是没有理解脏读和不可重复度两中问题的差异,以前所理解的事务内只会根据事务隔离级别的不同而选择是否可见其它 还未提交的事务的修正 ,天经地义的以为 已经提交事务的修正 对任何隔离级别的事务都是可见的,还是太天真了┐(´д`)┌ !
那么上面问题的缘故原由也很清晰了,由于事务隔离级别是mysql默认的 Repeatable Read
,这种隔离级别下要担保一个事务内前后读取到同样的数据,也就意味着对其它已提交或者未提交的修正都不可见,以是上述问题中方法a的事务对b方法中事务的修正选择不见(纵然已经提交)。
例行总结
java的上风就在于可以灵巧配置事务传播属性和事务隔离级别知足不同场景的数据库操作。但是利用过程中要清楚每种组合情形下可能会产生什么影响,例如我踩的坑是由于组合利用了 REQUIRES_NEW
和 REPATABLE READ
,导致子事务中提交的修正对外部事务不可见,同时还会造成外部事务发生非常回滚后,子事务并未回滚的问题。
我理解的 REQUIRES_NEW
的利用场景是内外事务之间完备独立,不须要担保数据同等性,而且可以有自己的隔离范围和锁,但是要确认和外部事务真的是完备独立的,也不须要担保内外数据同等性。我理解大部分场景下,更适宜利用 REQUIRED
或 NESTED
,最紧张是须要担保内外数据同等性。