-
Spring事务的隔离级别和传播行为
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL、C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
事务是恢复和并发控制的基本单位。
事务隔离级别定义了在一个事务中,哪些数据是对当前执行的语句“可见”的。在并发访问数据库时,事务隔离级别定义了多个事务之间对于同个目标数据源访问时的可交叉程度。
一、可交叉程度
1️⃣Dirty Read(脏读) 看到的数据则是不正确的
当一个事务能看见另外一个事务未提交的数据时,就称为脏读。如果这个事务被回滚了而不是提交了,那么其它事务看到的数据则是不正确的,是“脏”的。
2️⃣Non-repeatable Read(不可重复读) 两次读取到的数据不同
假设事务A读取了一行数据,接下来事务B改变了这行数据,之后事务A再一次读取这行数据,结果就是事务A两次读取到的数据不同。
3️⃣Phantom Read(幻读) 发现多出来一条数据
假设事务A通过一个 where 条件读取到了一个结果集,事务B这时插入了一条符合事务A的 where 条件的数据,之后事务A通过同样的 where 条件再次查询时,发现多出来一条数据。
二、事务隔离级别(Isolation)
JDBC 规范增加了隔离级别,来满足了 SQL:2003 定义的 4 种事务隔离级别。在安装MySQL时,安装默认的隔离级别就是:可重复读。
可以通过 select @@global.tx_isolation;
来查看当前隔离级别。隔离级别从最宽松到最严格,排序如下所示:
1️⃣TRANSACTION_NONE
这意味着当前的 JDBC 驱动不支持事务,也意味着这个驱动不符合 JDBC 规范。
2️⃣READ_UNCOMMITTED(读未提交)
允许事务看到其它事务修改了但未提交的数据,这意味着有可能是脏读、不可重复读或者幻读。
3️⃣READ_COMMITTED(读提交)
一个事务在未提交之前,所做的修改不会被其它事务所看见。这能避免脏读,但避免不了不可重复读和幻读。
4️⃣REPEATABLE_READ(可重复读取) MySQL默认的事务隔离级别
避免了脏读和不可重复读,但幻读依然是有可能发生的。
5️⃣SERIALIZABLE(序列化)
避免了脏读、不可重复读以及幻读。
三、Propagation事务传播行为
Propagation属性用来枚举事务的传播行为。所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring支持7种事务传播行为,默认为REQUIRED。
1️⃣REQUIRED
REQUIRED是常用的事务传播行为。如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
2️⃣SUPPORTS
SUPPORTS表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么这个方法会在这个事务中运行。
3️⃣MANDATORY
MANDATORY表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。不会主动开启一个事务。
4️⃣REQUIRES_NEW
REQUIRES_NEW表示当前方法必须运行在它自己的事务中。一个新的事务将被启动,如果存在当前事务,在该方法执行期间,当前事务会被挂起(如果一个事务已经存在,则先将这个存在的事务挂起)。如果使用JTATransactionManager的话,则需要访问TransactionManager。
5️⃣NOT_SUPPORTED
NOT_SUPPORTED表示该方法不应该运行在事务中,如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager。
6️⃣NEVER
NEVER表示当前方法不应该运行在事务上下文中,如果当前正有一个事务在运行,则会抛出异常。
7️⃣NESTED
NESTED表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与REQUIRED一样。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
综上所述,NESTED和REQUIRES_NEW非常相似,都是开启一个属于它自己的新事务。使用REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。两个事务互不影响,两个事务不是一个真正的嵌套事务,同时它还需要JTA事务管理器的支持。
使用NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。嵌套事务开始执行时, 它将取得一个 savepoint,如果这个嵌套事务失败, 将回滚到此savepoint。潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
四、Spring事务的基本原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
- 获取连接 Connection con = DriverManager.getConnection();
- 开启事务 con.setAutoCommit(true/false);
- 执行 CRUD;
- 提交事务/回滚事务 con.commit()/con.rollback();
- 关闭连接 con.close()。
使用Spring的事务管理功能后,程序员可以不再写步骤 2 和 4 的代码,而是由 Spirng 自动完成。Spring是如何CRUD之前和之后开启事务和关闭事务的?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子:
- 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
- Spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transactional的相关参数进行相关配置注入,这样就在代理中把相关的事务实现了(开启正常提交事务,异常回滚事务)。
- 真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
五、原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
事务最经典的例子就是转账了。假如小明要给小红转账100元,这个转账会涉及到两个关键操作就是:将小明的余额减少100元,将小红的余额增加100元。万一在这两个操作之间突然出现错误(如银行系统崩溃等),导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
1️⃣原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
2️⃣一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
3️⃣隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4️⃣持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
__EOF__
转载自:https://www.jianshu.com/p/7e76ce65e3ad