抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了Spring支持的事务管理。

环境

Windows 10 企业版 LTSC 21H2
MySQL 5.7.40
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.2.25.RELEASE

1 定义

事务是一组逻辑处理单位,可以是执行一条SQL语句,也可以是执行几个SQL语句。

事务用来保证数据由一种存储情况变为另一种情况,组成事务的各个单元要么都执行成功,要么都执行失败。

2 事务特性

事务有四种特性:

  • 原子性(Atomicity):事务是不可分割的最小执行单元,要么所有操作全部执行成功,要么全部失败回滚,不存在部分执行的中间状态。
  • 一致性(Consistency):事务执行前后,数据库的所有完整性约束始终保持有效,数据从一个合法的一致状态平稳过渡到另一个合法的一致状态。
  • 隔离性(Isolation):并发执行的事务之间互不干扰,每个事务的操作中间状态对其他事务不可见。
  • 持久性(Durability):事务一旦提交成功,其对数据库做出的所有修改都会被永久保存到磁盘中,即使系统发生故障也不会丢失。

3 并发问题

对事务的操作分为两类:一种是读取事务(查),另一种是修改事务(增删改)。

单个事务的情况下,不会产生并发问题。如果多个事务在同一时刻操作同一数据可能会影响最终期望的结果,产生并发问题。

常见的并发问题:

  • 脏写:更新时更新。事务一更新数据未提交,事务二更新相同数据并提交,事务一回滚,事务二提交的更新数据无效。
  • 脏读:更新时读取。事务一更新数据未提交,事务二读取数据,事务一回滚,事务二读取的数据无效。
  • 不可重复读:读取时更新或者删除。事务一读取数据,事务二修改数据或者删除数据,在事务二提交,事务一读取到不同的数据。
  • 幻读:读取时插入。事务一读取数据,事务二插入数据,在事务二提交,事务一读取到插入的数据。

更新丢失:

  • 第一类更新丢失:更新时更新。事务一更新数据并提交,事务二更新相同数据并回滚,事务一提交的更新数据无效。
  • 第二类更新丢失:更新时更新。事务一更新数据并提交,事务二更新相同数据并提交,事务一提交的更新数据无效。

数据库不允许第一类更新丢失问题,第二类更新丢失问题无法通过隔离级别解决,一般使用锁解决。

4 隔离级别

为了解决不同程度的并发问题,SQL标准定义了隔离级别,每个级别都有各自的具体规则。

主要的隔离级别有四种:

  • 读未提交(RU,Read Uncommitted):最低的隔离级别,允许读取未提交的数据变更,但是更新相同数据会被阻塞。可避免脏写。
  • 读已提交(RC,Read Committed):大多数系统的默认隔离级别,允许读取已提交的数据变更。可避免脏写、脏读。
  • 可重复读(RR,Repeatable Read):MySQL默认隔离级别,对同一字段的多次读取结果是一致的,除非数据被当前事务本身改变。可避免脏写、脏读、不可重复读。
  • 串行化(S,Serializable):最高的隔离级别,通过强制事务排序解决并发问题。在每个读操作的数据行增加共享锁,可能导致大量超时和竞争。可避免脏写、脏读、不可重复读、幻读。

MySQL的默认隔离级别是RR级别,InnoDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了幻读的问题。

5 传播行为

5.1 概念

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为由传播属性决定,事务传播属性可以在@Transactional注解的propagation属性中定义。

5.2 分类

速记:

  • REQUIRED:有则加入,无则新建
  • REQUIRES_NEW:有也新建,无也新建
  • SUPPORTS:有则加入,无则忽略
  • NOT_SUPPORTED:有也忽略,无也忽略
  • MANDATORY:有则加入,无则报错
  • NEVER:有则报错,无则忽略
  • NESTED:有则嵌套,无则新建

5.2.1 REQUIRED

默认传播行为。

有则加入,无则新建:

  • 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
  • 如果没有事务方法调用,当前方法就开启一个新的事务执行。

5.2.2 REQUIRES_NEW

有也新建,无也新建:

  • 如果在其他事务方法里调用当前事务方法,其他方法的事务将会被挂起,当前方法开启一个新的事务执行,当前方法执行完成后再继续执行其他方法的事务。
  • 如果没有事务方法调用,当前方法就开启一个新的事务执行。

5.2.3 SUPPORTS

有则加入,无则忽略:

  • 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
  • 如果没有事务方法调用,当前方法就不在事务中执行。

5.2.4 NOT_SUPPORTED

有也忽略,无也忽略:

  • 如果在其他事务方法里调用当前事务方法,其他方法的事务将会被挂起,当前方法不在事务中执行,当前方法执行完成后再继续执行其他方法的事务。
  • 如果没有事务方法调用,当前方法就不在事务中执行。

5.2.5 MANDATORY

有则加入,无则报错:

  • 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
  • 如果没有事务方法调用,当前方法就抛出一个异常。

5.2.6 NEVER

有则报错,无则忽略:

  • 如果在其他事务方法里调用当前事务方法,当前方法就抛出一个异常。
  • 如果没有事务方法调用,当前方法就不在事务中执行。

5.2.7 NESTED

有则嵌套,无则新建:

  • 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务的嵌套中执行。子事务回滚时不会导致主事务回滚,主事务提交和回滚时会导致子事务一起提交和回滚。
  • 如果没有事务方法调用,当前方法就开启一个新的事务执行。

6 事务管理

6.1 编程式事务管理

在使用编程式事务管理时,必须在每个事务操作中包含额外的事务管理代码,通过将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。

特点:

  • 使用原生的事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。
  • 相对于核心业务而言,事务管理代码显然属于非核心业务,会造成较大程度的代码冗余。

6.2 声明式事务管理

大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

将事务管理方法作为横切关注点,通过AOP支持声明式事务管理。

特点:

  • 业务代码中不包含任何事务管理代码。代码只关注核心业务逻辑,保持简洁和纯粹。
  • 当事务需求改变时,通常只需修改配置或注解属性,而无需深入修改大量业务代码,降低了维护成本和风险。

评论