spring事务详解(二)简单样例mysql
spring事务详解(三)源码详解spring
不少coder在不理解事务的原理甚至连基本概念都不清楚的状况下,就去使用数据库事务,是极容易出错,写出一些本身不能掌控的代码。网上不少文章要不就是概念,或者一点源码,或者一点测试验证,都不足以全面了解事务,因此本文出现了,本系列Spring事务详解包含四部分:缓存
第一章 讲概念,对事务的总体有一个了解。session
第二章 简单样例,了解如何用。并发
第三章 从源码来看底层实现机制。数据库设计
第四章 实例测试验证。分布式
第五章 总结提升。
我的能力有限,有任何不当之处,麻烦指出。
全文基于Mysql innodb引擎。Mysql官方文档:官网飞机票 ,推荐书籍:《Mysql技术内幕-InnoDB存储引擎》。
spring事务领头人叫Juergen Hoeller,于尔根·糊了...先混个脸熟哈,他写了几乎所有的spring事务代码。读源码先拜神,掌握他的源码的风格,读起来会通畅不少。最后一节我们总结下这个大神的代码风格。
事务(Transaction)是数据库区别于文件系统的重要特性之一。目前国际承认的数据库设计原则是ACID特性,用以保证数据库事务的正确执行。Mysql的innodb引擎中的事务就彻底符合ACID特性。
spring对于事务的支持,分层概览图以下:
(箭头后,翻译自官网介绍:InnoDB and the ACID Model )
原子性(Atomicity):一个事务必须被视为一个不可分割的最小工做单元,整个事务中的全部操做要么所有提交成功,要么所有失败回滚。--》主要涉及InnoDB事务。相关特性:事务的提交,回滚,信息表。
一致性(consistency):数据库老是从一个一致性的状态转换到另外一个一致性的状态。在事务开始先后,数据库的完整性约束没有被破坏。例如违反了惟一性,必须撤销事务,返回初始状态。--》主要涉及内部InnoDB处理,以保护数据不受崩溃,相关特性:双写缓冲、崩溃恢复。
隔离性(isolation):每一个读写事务的对象对其余事务的操做对象能相互分离,即:事务提交前对其余事务是不可见的,一般内部加锁实现。--》主要涉及事务,尤为是事务隔离级别,相关特性:隔离级别、innodb锁的底层实现细节。
持久性(durability):一旦事务提交,则其所作的修改会永久保存到数据库。--》涉及到MySQL软件特性与特定硬件配置的相互影响,相关特性:4个配置项:双写缓冲开关、事务提交刷新log的级别、binlog同步频率、表文件;写缓存、操做系统对于fsync()的支持、
备份策略等。
要保证事务的ACID特性,spring给事务定义了6个属性,对应于声明式事务注解(org.springframework.transaction.annotation.Transactional)@Transactional(key1=*,key2=*...)
其中隔离级别和传播机制比较复杂,我们细细地品一品。
这一块比较复杂,咱们从3个角度来看:3种错误现象、mysql的底层技术支持、分级处理策略。这一小节必定要好好看,已经开始涉及核心原理了。
脏读(Drity Read):事务A更新记录但未提交,事务B查询出A未提交记录。
不可重复读(Non-repeatable read): 事务A读取一次,此时事务B对数据进行了更新或删除操做,事务A再次查询数据不一致。
幻读(Phantom Read): 事务A读取一次,此时事务B插入一条数据事务A再次查询,记录多了。
官网飞机票:InnoDB Transaction Model
在MVCC中,读操做能够分红两类,快照读(Snapshot read)和当前读(current read)。
快照读:普通的select
当前读:
其中前两种锁定读,须要用户本身显式使用,最后一种是自动添加的。
一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎经过多版本控制(multi versionning)的方式来读取当前执行时间数据库中行的数据,若是读取的行正在执行DELETE或UPDATE操做,这是读取操做不会所以等待行上锁的释放。相反的,InnoDB会去读取行的一个快照数据
上面展现了InnoDB存储引擎一致性的非锁定读。之因此称为非锁定读,由于不须要等待访问的行上X锁的释放。快照数据是指该行以前版本的数据,该实现是经过undo段来完成。而undo用来事务中的回滚数据,所以快照数据自己没有额外的开销,此外,读取快照数据不须要上锁,由于没有事务须要对历史数据进行修改操做。
innoDB对select语句支持两种锁定读:
1)SELECT...FOR UPDATE:对读取的行加排它锁(X锁),其余事务不能对已锁定的行再加任何锁。
2 ) SELECT...LOCK IN SHARE MODE :对读取的行加共享锁(S锁),其余事务能够再加S锁,X锁会阻塞等待。
注:这两种锁都必须处于事务中,事务commit,锁释放。因此必须begin或者start transaction 开启一个事务或者索性set autocommit=0把自动提交关掉(mysql默认是1,即执行完sql当即提交)
官网描述:
InnoDB使用不一样的锁定策略支持每一个事务隔离级别。对于关键数据的操做(听从ACID原则),您可使用强一致性(默认Repeatable Read)。对于不是那么重要的数据操做,可使用Read Committed/Read Uncommitted。Serializable执行比可重读更严格的规则,用于特殊场景:XA事务,并发性和死锁问题的故障排除。
四种隔离级别:
1.Read Uncommitted(读取未提交内容):可能读取其它事务未提交的数据。-脏读问题(脏读+不可重复读+幻读)
2.Read Committed(读取提交内容):一个事务只能看见已经提交事务所作的改变。(不可重复读+幻读)
select...from : 一致性非锁定读的数据快照(MVCC)是最新版本的,但其余事务可能会有新的commit,因此同一select可能返回不一样结果。-不可重复读问题
select...from for update : record lock行级锁.
3.Repeatable Read(可重读):
select…from :同一事务内屡次一致性非锁定读,取第一次读取时创建的快照版本(MVCC),保证了同一事务内部的可重复读.—狭义的幻读问题获得解决。(Db插入了数据,只不过读不到)
select...from for update (FOR UPDATE or LOCK IN SHARE MODE), UPDATE, 和 DELETE : next-key lock下一键锁.
1)对于具备惟一搜索条件的惟一索引,innoDB只锁定找到的索引记录. (next-key lock 降为record lock)
2)对于其余非索引或者非惟一索引,InnoDB会对扫描的索引范围进行锁定,使用next-key locks,阻塞其余session对间隙的insert操做,-完全解决广义的幻读问题。(DB没插入数据)
4.Serializable(可串行化):这是最高的隔离级别,它是在每一个读的数据行上加上共享锁(LOCK IN SHARE MODE)。在这个级别,可能致使大量的超时现象和锁竞争,主要用于分布式事务。
以下表:
不一样隔离级别/可能出现的问题 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted(读取未提交内容) | ✅ | ✅ | ✅ |
Read Committed(读取提交内容) | ❎ | ✅ | ✅ |
Repeatable Read(可重读) | ❎ | ❎ | ✅ |
Serializable(可串行化) | ❎ | ❎ | ❎ |
org.springframework.transaction包下有一个事务定义接口TransactionDefinition,定义了7种事务传播机制,不少人对传播机制的曲解从概念开始,因此特意翻译了一下源码注释以下:
支持当前事务;若是不存在,建立一个新的。相似于同名的EJB事务属性。这一般是事务定义的默认设置,一般定义事务同步做用域。
支持当前事务;若是不存在事务,则以非事务方式执行。相似于同名的EJB事务属性。
注意:
对于具备事务同步的事务管理器,PROPAGATION_SUPPORTS与没有事务稍有不一样,由于它可能在事务范围内定义了同步。所以,相同的资源(JDBC的Connection、Hibernate的Session等)将在整个指定范围内共享。注意,确切的行为取决于事务管理器的实际同步配置!
当心使用PROPAGATION_SUPPORTS!特别是,不要依赖PROPAGATION_REQUIRED或PROPAGATION_REQUIRES_NEW,在PROPAGATION_SUPPORTS范围内(这可能致使运行时的同步冲突)。若是这种嵌套不可避免,请确保适当地配置事务管理器(一般切换到“实际事务上的同步”)。
支持当前事务;若是当前事务不存在,抛出异常。相似于同名的EJB事务属性。
注意:
PROPAGATION_MANDATORY范围内的事务同步老是由周围的事务驱动。
建立一个新事务,若是存在当前事务,则挂起当前事务。相似于同名的EJB事务属性。
注意:实际事务挂起不会在全部事务管理器上开箱即用。这一点特别适用于JtaTransactionManager,它须要TransactionManager的支持。
PROPAGATION_REQUIRES_NEW范围老是定义本身的事务同步。现有同步将被挂起并适当地恢复。
不支持当前事务,存在事务挂起当前事务;始终以非事务方式执行。相似于同名的EJB事务属性。
注意:实际事务挂起不会在全部事务管理器上开箱即用。这一点特别适用于JtaTransactionManager,它须要TransactionManager的支持。
事务同步在PROPAGATION_NOT_SUPPORTED范围内是不可用的。现有同步将被挂起并适当地恢复。
不支持当前事务;若是当前事务存在,抛出异常。相似于同名的EJB事务属性。
注意:事务同步在PROPAGATION_NEVER范围内不可用。
若是当前事务存在,则在嵌套事务中执行,若是当前没有事务,相似PROPAGATION_REQUIRED(建立一个新的)。EJB中没有相似的功能。
注意:实际建立嵌套事务只对特定的事务管理器有效。开箱即用,这只适用于 DataSourceTransactionManager(JDBC 3.0驱动)。一些JTA提供者也可能支持嵌套事务。
本节讲解了事务的4大特性和6大属性的概念。并简单拓展了一下概念。可能你们会比较懵逼哈,不用担忧只须要内心有个概念就能够了,下一章我们从底层源码来看事务的实现机制。下面是隔离级别的表格,
注意:JtaTransactionManager的类注释上说:Transaction suspension (REQUIRES_NEW, NOT_SUPPORTED) is just available with a JTA TransactionManager being registered." 这是片面的,只是说JTA TransactionManager支持挂起,并无说DataSourceTransactionManager不支持。通过第四节实测,发现彻底是支持的。网上不少说REQUIRES_NEW、NOT_SUPPORTED必需要JTA TransactionManager才行的彻底是错误的说法。
不一样传播机制 | 事务名称 | 描述 | 事务管理器要求 | 是否支持事务 | 是否开启新事务 | 回滚规则 |
REQUIRED |
要求 |
存在加入,不存在建立新 |
无 |
✅ |
不必定 |
存在一个事务:1.外部有事务加入,异常回滚;2.外部没事务建立新事务,异常回滚 |
SUPPORTS |
支持 |
存在加入,不存在非事务 |
无 |
✅ |
❎ |
最多只存在一个事务: 1.外部有事务加入,异常回滚;2.外部没事务,内部非事务,异常不回滚 |
MANDATORY |
强制 |
存在加入,不存在抛异常 |
无 |
✅ |
❎ |
最多只存在一个事务: 1.外部存在事务加入,异常回滚;2.外部不存在事务,异常没法回滚 |
REQUIRES_NEW |
要求新 |
存在挂起建立新,不存在建立新 |
无 | ✅ |
✅ |
可能存在1-2个事务:1.外部存在事务挂起,建立新,异常回滚本身的事务 2.外部不存在事务,建立新, 异常只回滚新事务 |
NOT_SUPPORTED |
不支持 |
存在挂起,不存在非事务 |
无 |
❎ |
❎ |
最多只存在一个事务:1. 外部有事务挂起,外部异常回滚;内部非事务,异常不回滚2.外部无事务,内部非事务,异常不回滚 |
NEVER |
坚定不 |
存在抛异常 |
无 |
❎ |
❎ |
最多只存在一个事务:1.外部有事务,外部异常回滚;内部非事务不回滚 2.外部非事务,内部非事务,异常不回滚 |
NESTED |
嵌套 |
存在嵌套,不存在建立新 |
DataSourceTransactionManager |
✅ |
❎(同一个物理事务,保存点实现嵌套) |
存在一个事务:1. 外部有事务,嵌套事务建立保存点,外部异常回滚所有事务;内部嵌套事务异常回滚到保存点;2.外部不存在事务,内部建立新事务,内部异常回滚 |
=======参考========
Mysql官方文档:官网飞机票
书籍:《Mysql技术内幕-InnoDB存储引擎》