spring事务详解(一)概览

系列目录

spring事务详解(一)初探事务

spring事务详解(二)源码详解

spring事务详解(三)测试验证

spring事务详解(四)总结提高

引子

很多coder在不理解事务的原理甚至连基本概念都不清楚的情况下,就去使用数据库事务,是极容易出错,写出一些自己不能掌控的代码。网上很多文章要不就是概念,或者一点源码,或者一点测试验证,都不足以全面了解事务,所以本文出现了,本系列Spring事务详解包含四部分:

第一章 讲概念,对事务的整体有一个了解。

第二章 从源码来看底层实现机制。

第三章 实例测试验证。

第四章 总结提高。

这次编写,期望一起学习提高,个人能力有限,有任何不当之处,麻烦指出。

全文基于Mysql innodb引擎。Mysql官方文档:官网飞机票 ,推荐书籍:《Mysql技术内幕-InnoDB存储引擎》。

一、背景

spring事务领头人叫Juergen Hoeller,于尔根·糊了...先混个脸熟哈,他写了几乎全部的spring事务代码。读源码先拜神,掌握他的源码的风格,读起来会通畅很多。

事务(Transaction)是数据库区别于文件系统的重要特性之一。目前国际认可的数据库设计原则是ACID特性,用以保证数据库事务的正确执行。Mysql的innodb引擎中的事务就完全符合ACID特性。

二、事务的ACID特性

(箭头后,翻译自官网介绍: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=*...)

  • 事务名称:用户可手动指定事务的名称,当多个事务的时候,可区分使用哪个事务。对应注解中的属性value、transactionManager
  • 隔离级别:  为了解决数据库容易出现的问题,分级加锁处理策略。 对应注解中的属性isolation
  • 超时时间:  定义一个事务执行过程多久算超时,以便超时后回滚。可以防止长期运行的事务占用资源.对应注解中的属性timeout
  • 是否只读:表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.对应注解中的属性readOnly
  • 传播机制:  对事务的传播特性进行定义,共有7种类型。对应注解中的属性propagation
  • 回滚机制:定义遇到异常时回滚策略。对应注解中的属性rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName

 其中隔离级别传播机制比较复杂,咱们细细地品一品。

3.1 隔离级别

这一块比较复杂,我们从3个角度来看:3种错误现象、mysql的底层技术支持、分级处理策略。这一小节一定要好好看,已经开始涉及核心原理了。

 1.现象(三种问题)

脏读(Drity Read):事务A更新记录但未提交,事务B查询出A未提交记录。

不可重复读(Non-repeatable read): 事务A读取一次,此时事务B对数据进行了更新或删除操作,事务A再次查询数据不一致。

幻读(Phantom Read): 事务A读取一次,此时事务B插入一条数据事务A再次查询,记录多了。

2. mysql的底层支持一致性非锁定读VS锁定读)
  • 一致性非锁定读(mysql隐性处理)

  一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过多版本控制(multi versionning)的方式来读取当前执行时间数据库中行的数据,如果读取的行正在执行DELETE或UPDATE操作,这是读取操作不会因此等待行上锁的释放。相反的,InnoDB会去读取行的一个快照数据

 

上面展示了InnoDB存储引擎一致性的非锁定读。之所以称为非锁定读,因为不需要等待访问的行上X锁的释放。快照数据是指该行之前版本的数据,该实现是通过undo段来完成。而undo用来事务中的回滚数据,因此快照数据本身没有额外的开销,此外,读取快照数据不需要上锁,因为没有事务需要对历史数据进行修改操作。在MVCC中,读操作可以分成两类,快照读(Snapshot read)和当前读(current read)。

快照读:普通的select

当前读

  1. select * from table where ? lock in share mode; (加S锁)
  2. select * from table where ? for update; (加X锁)
  3. insert, update, delete 操作前会先进行一次当前读(加X锁)
  • 锁定读(用户自己显式使用)

innoDB对select语句支持两种锁定读(意向锁Intention Locks):

1)SELECT...FOR UPDATE:对读取的行加排它锁(X锁),其他事务不能对已锁定的行再加任何锁。

2 ) SELECT...LOCK IN SHARE MODE :对读取的行加共享锁(S锁),其他事务可以再加S锁,X锁会阻塞等待。

注:这两种锁都必须处于事务中,事务commit,锁释放。所以必须begin或者start transaction 开启一个事务或者索性set autocommit=0把自动提交关掉(mysql默认是1,即执行完sql立即提交)

3. 分级处理策略(四种隔离级别)

官网描述:

InnoDB使用不同的锁定策略支持每个事务隔离级别。对于关键数据的操作(遵从ACID原则),您可以使用强一致性(默认Repeatable Read)。对于不是那么重要的数据操作,可以使用Read Committed/Read Uncommitted。Serializable执行比可重读更严格的规则,用于特殊场景:XA事务,并发性和死锁问题的故障排除。

四种隔离级别:

1.Read Uncommitted(读取未提交内容):可能读取其它事务未提交的数据。(脏读+不可重复读+幻读)

2.Read Committed(读取提交内容):一个事务只能看见已经提交事务所做的改变。(RC隔离级别下,一致性非锁定读的数据快照是最新版本的。)但其他事务可能会有新的commit,所以同一select可能返回不同结果。(不可重复读+幻读)

3.Repeatable Read(可重读)

  1.实现可重复读:同一事务内多次一致性非锁定读,RR会取第一次读取时建立的快照版本,从而保证了同一事务内部的可重复读,这也是innodb的默认隔离级别。

  2.解决幻读问题,采用加锁解决。锁定读(FOR UPDATE or LOCK IN SHARE MODE), UPDATE, 和 DELETE,具体锁一行还是锁多行,如下:

    1)对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录。

    2)对于其他搜索条件,InnoDB会对扫描的索引范围进行锁定,使用next-key locks,阻塞其他session对间隙的insert操作。所以RR隔离级别下,用户必须自己使用加锁读解决幻读问题!!!

4.Serializable(可串行化):这是最高的隔离级别,它是在每个读的数据行上加上共享锁(LOCK IN SHARE MODE)。在这个级别,可能导致大量的超时现象和锁竞争,主要用于分布式事务

如下表:

mysql-innodb可能出现的问题统计表
不同隔离级别/可能出现的问题脏读不可重复读幻读
Read Uncommitted(读取未提交内容)
Read Committed(读取提交内容)
Repeatable Read(可重读)

Serializable(可串行化)

 

 

 

 

 

 

 

 

 

上表需要特别注意的是RR级别下mysql innodb还是有幻读现象的,只是需要用户必须自己使用加锁读解决幻读问题!

3.2 传播机制

org.springframework.transaction包下有一个事务定义接口TransactionDefinition,定义了7种事务传播机制,很多人对传播机制的曲解从概念开始,所以特地翻译了一下源码注释如下:

1.PROPAGATION_REQUIRED

支持当前事务;如果不存在,创建一个新的。类似于同名的EJB事务属性。这通常是事务定义的默认设置,通常定义事务同步作用域。

2.PROPAGATION_SUPPORTS

支持当前事务;如果不存在事务,则以非事务方式执行。类似于同名的EJB事务属性。
注意:
对于具有事务同步的事务管理器,PROPAGATION_SUPPORTS与没有事务稍有不同,因为它可能在事务范围内定义了同步。因此,相同的资源(JDBC的Connection、Hibernate的Session等)将在整个指定范围内共享。注意,确切的行为取决于事务管理器的实际同步配置!
小心使用PROPAGATION_SUPPORTS!特别是,不要依赖PROPAGATION_REQUIRED或PROPAGATION_REQUIRES_NEW,在PROPAGATION_SUPPORTS范围内(这可能导致运行时的同步冲突)。如果这种嵌套不可避免,请确保适当地配置事务管理器(通常切换到“实际事务上的同步”)。

3.PROPAGATION_MANDATORY

支持当前事务;如果当前事务不存在,抛出异常。类似于同名的EJB事务属性。
注意:

PROPAGATION_MANDATORY范围内的事务同步总是由周围的事务驱动。

4.PROPAGATION_REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起当前事务。类似于同名的EJB事务属性。
注意:实际事务挂起不会在所有事务管理器上开箱即用。这一点特别适用于JtaTransactionManager,它需要TransactionManager的支持。
PROPAGATION_REQUIRES_NEW范围总是定义自己的事务同步。现有同步将被挂起并适当地恢复。

5.PROPAGATION_NOT_SUPPORTED

不支持当前事务;始终以非事务方式执行。类似于同名的EJB事务属性。
注意:实际事务挂起不会在所有事务管理器上开箱即用。这一点特别适用于JtaTransactionManager,它需要TransactionManager的支持。
事务同步在PROPAGATION_NOT_SUPPORTED范围内是不可用的。现有同步将被挂起并适当地恢复。

6.PROPAGATION_NEVER

不支持当前事务;如果当前事务存在,抛出异常。类似于同名的EJB事务属性。

注意:事务同步在PROPAGATION_NEVER范围内不可用

7.PROPAGATION_NESTED

如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,类似PROPAGATION_REQUIRED。EJB中没有类似的功能。
注意:实际创建嵌套事务只对特定的事务管理器有效。开箱即用,这只适用于 DataSourceTransactionManager(JDBC 3.0驱动)。一些JTA提供者也可能支持嵌套事务。

四、总结

本节讲解了事务的4大特性和6大属性的概念。并简单拓展了一下概念。可能大家会比较懵逼哈,不用担心只需要心里有个概念就可以了,下一章咱们从底层源码来看事务的实现机制。


=======参考========

Mysql官方文档:官网飞机票

书籍:《Mysql技术内幕-InnoDB存储引擎》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值