(一)事务的隔离级别
- 大家都知道事务有四个属性,即ACID(原子性、一致性、隔离性、持久性)。这四个里面稍微难理解点的是一致性和持久性。所谓的一致性是指:事务执行前后数据的一致性状态,例如事务执行前用户有1万元,事务回滚后用户仍应该有1万元。而这里的持久性指的是:事务在提交后结果是永久的,即使程序崩溃数据也能恢复,当然数据库的持久性指的是高可靠性,事务执行后数据保证一定写到了磁盘及备份日志,但并不是指高可用性 ,如果你强行把机器的磁盘烧了,那数据也是无法恢复的。
- 在事务隔离性的基础上,我们对事务间的相互影响程度进行定级,即事务的隔离级别。通常事务有四种隔离级别:read uncommitted、read committed、repeatable read、serializable。
(1)read uncommitted是最低的隔离级别,其实就如其英文名一样,处于这个级别的事务可能会读到其他事务尚未提交的数据修改,即存在脏读问题。除此之外,还存在不可重复读和幻读问题问题。
(2)read committed是大部分数据库的默认隔离级别,它解决了脏读问题,但是仍存在不可重复读和幻读问题。
(3)repeatable read解决了不可重复读的问题,但是仍可能存在幻读的问题。值得一提的是mysql的默认级别是repeatable read,但它却不存在幻读问题,因为mysql通过使用next-key lock技术解决了问题,即使在repeatable read级别也能完全保证事务的隔离性要求。
(4) serializable是最为严格的隔离级别,所有事务顺序执行,实际上是通过将并行转为串行来解决事务隔离问题,无疑会降低性能。
不可重复读和幻读的区别:
不可重复读指在同一事务中多次查询同一记录(eg:select * from xx where id = 1),查询结果不一致。这主要是由于多次读期间其他事务update或delete了记录。
幻读是指在同一事务中多次进行范围查询(eg:select count(*) from xx where id > 1 and id < 100),查询结果不一致。这主要是由于多次范围查询期间其他事务insert了新数据。
(二)事务的参与者及分类
在一般的事务场景中通常有以下几个参与者:
- RM:用于管理系统数据资源,即通常意义上的数据库服务器等。
- TP monitor:主要用于分布式事务,用来协调处理多个RM的事务处理。
- TM:事务的管理者,负责事务界定、事务上下文传播等功能。
- Applacation:运行于容器中的应用程序,例如spring应用程序。
我们通常会根据RM的数量将事务分为分布式事务和局部事务(本地事务)两种。 在分布式事务中通常会存在多个不同的RM,这些RM分布在不同的系统中,相互之间通过TP monitor协调,利用两阶段提交来保障事务的ACID属性。在局部事务中一般每次数据操作只有一个RM,数据操作只需操作一个数据库,在同一个事务中不会进行多个数据库的更新。接下来我们将着重介绍局部事务的实现,毕竟分布式事务我懂的也不多😜。
(三)局部事务详解
1.事务原理简介
首先我们需要了解的很重要的一点是,spring的事务底层实现本质上是依赖于其使用的数据库服务器,例如mysql数据库。在mysql数据库中事务的隔离依赖于mysql的锁机制,持久性和原子依赖于redo日志,而一致性依赖于undo日志。mysql提供事务功能的具体实现,spring负责界定事务的边界及定义事务的传播性,然后通过特定的数据访问技术(例:jdbc、hibernate等)的API对事务进行管理。
我们以jdbc举个简单的例子,我们可以看下面的代码:
Connection connection = null;boolean rollback = false;try {connection = dataSource.getConnection();connection.setAutoCommit(false);// 数访问XXXXXXXXXXXX;connection.commit();} catch (SqlException e) {rollback = true;} finally {if (rollback) {connection.rollback();} else {connection.close();}}
这就是我们利用jdbc进行的最简单的事务管理,将autoCommit置为false,然后根据各种情况进行事务的回滚提交等操作。而我们平时普通的数据操作都是默认自动提交,mysql会自动隐式的帮我们commit事务。
但是上面的写法在实际的开发中存在很多的问题:1.不同的数数据访问技术有不同的api、2.我们需要自己手动捕获处理各种以sql异常、3.事务管理代码和数据访问代码及业务代码耦合在一起。
2.spring事务框架重要类
为了解决上面提到的问题,spring为我们提供了优秀的事务框架。我们先来介绍下事务框架中重要的类:
顶层接口 | 职责 |
---|---|
TransactionDefinition | 用来定义事务属性,包括事务的隔离性、传播性、超时时间、是否只读等属性。 |
TransactionStatus | 用来记录事务开启到事务结束期间事务的状态,这个类一般在编程式事务中使用。 |
PlatformTransactionManager | spring事务框架的核心类,负责事务的管理,包括事务的commit、rollback等。 |
3.spring事务传播性
事务传播性 | 说明 |
---|---|
REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务,这是spring默认的传播行为。 |
SUPPORTS | 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。 |
MANDATORY | 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常。 |
REQUIRES_NEW | 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行。 |
NOT_SUPPORTED | 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。 |
NEVER | 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常。只有没关联到事务,才正常执行。 |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动的事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保证点。内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效。 |