目录
1.事务说明
2.事务及数据库的隔离级别
3.事务的传播行为
4.声明是事务
5.编程式事务
6.避免长事务的方式
1.事务说明
数据库的事务是一组操作的集合,这些操作要么全部成功,要么全部失败。用于确保事务的一致性及完整性,事务的主要特性可以通过ACID原则来概括
-
原子性 (Atomicity):
事务中的所有操作要么全部完成,要么全部不完成。如果事务中任一操作失败,整个事务都会被回滚,保持数据在执行前的状态。 -
一致性 (Consistency):
事务必须使数据库从一个一致性状态转换到另一个一致性状态。在事务开始之前和结束之后,数据库的完整性约束必须得到满足。 -
隔离性 (Isolation):
并发执行的事务之间应相互独立,一个事务的执行不应影响其他事务的执行。不同的隔离级别会影响事务的并发行为,如未提交读、已提交读、可重复读和串行化等。 -
持久性 (Durability):
一旦事务提交,其结果是永久性的,即使系统发生崩溃,提交的数据也不会丢失。数据库管理系统通常通过日志或其他持久存储机制来实现这一点。
2.事务及数据库的隔离级别
Read Uncommitted(读未提交)
- 描述:在这个级别下,一个事务可以读取另一个事务未提交的数据。这意味着事务可以看到其他事务对数据所做的更改,即使这些更改尚未被提交。
- 优点:性能最高,因为不需要锁定数据。
- 缺点:可能导致脏读、不可重复读和幻读。
Read Committed(读已提交)
- 描述:只有已经提交的事务所做的更改才能被读取。在此级别下,事务在读取数据之前会等待其他事务提交。
- 优点:避免了脏读问题,但仍可能导致不可重复读和幻读。
- 缺点:虽然性能比
Read Uncommitted
略低,但总体上仍然比较高效。
Repeatable Read(可重复读)
- 描述:在一个事务中,如果读取某个数据行,那么在该事务结束之前,不管其他事务如何更改该数据行,该行的值将始终保持不变。即使其他事务已经提交了更改,当前事务也看不到这些变化。
- 优点:防止了脏读和不可重复读,但仍可能发生幻读情况。
- 缺点:增加了性能开销,尤其是在高并发场景下。
Serializable(可串行化)
- 描述:这是最严格的隔离级别,在这个级别下,事务完全隔离。事务的执行就好像所有事务是串行执行的一样,不会有任何干扰。
- 优点:绝对的一致性,防止所有并发问题,包括脏读、不可重复读和幻读。
- 缺点:性能最低,因为它可能会导致大量的锁竞争和阻塞。
在Spring中,你可以通过@Transactional
注解来设置事务的隔离级别。例如:
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void myTransactionalMethod() {
// 业务逻辑
}
脏读:指的是一个事务读取了另一个尚未提交的事务所修改的数据。这种情况可能导致读取到的数据不可靠,因为被读取的数据可能会在后续被回滚,从而使得第一个事务得到的信息变得无效。
不可重复读:它指的是在同一事务中,对同一数据的两次读取可能得到不同的结果。这种情况通常发生在以下场景中:
- 事务A读取了某个数据项(例如,价格)。
- 事务B在事务A的读取后对该数据项进行了修改。
- 事务A再次读取同一数据项时,可能会发现数据已被事务B修改过,从而得到不同的结果。
幻读:指在同一事务中,反复执行相同的查询操作时,结果集会出现不同的数据情况。具体来说,当一个事务在读取数据后,另一个事务对数据库进行了插入或删除操作,导致第一个事务再次执行相同查询时,能够看到新插入的行(或缺失被删除的行),这就称为幻读
3.事务的传播行为
描述了当一个事务调用另一个事务时,如何处理事务边界和事务上下文。常见的传播行为包括:
- REQUIRED:如果存在当前事务,则加入该事务;如果没有,则创建新事务。
- REQUIRES_NEW:总是创建一个新的事务,如果存在当前事务则挂起它。
- NESTED:支持嵌套事务,允许在当前事务中开启一个子事务。
- MANDATORY:要求当前操作必须在一个已有的事务中进行。如果存在当前事务,则加入该事务;如果没有,则抛出异常
- SUPPORTS:如果有当前事务则参与,否则以非事务方式执行。
- NOT_SUPPORTED:不支持事务,如果存在事务则挂起。
- NEVER:不支持事务,若存在事务则抛出异常。
4.声明是事务
声明式事务通过Transactional注解进行实现,是基于AOP的,本质是在方法执行前后进行拦截,在方法执行前开启事务,在方法之后后按照不同的情况进行事务的提交和回滚。
Transactional注解的参数说明
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
//当配置了多个事务管理器时,指定选择哪一个事务管理器
@AliasFor("transactionManager")
String value() default "";
//和上个参数一致
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
//设置事务的传播行为
Propagation propagation() default Propagation.REQUIRED;
//设置事务的隔离级别
Isolation isolation() default Isolation.DEFAULT;
//事务的超时时间
int timeout() default -1;
//和上面一样
String timeoutString() default "";
//指定事务是否为只读事务,为了忽略那些不需要事务的方法,比如读取数据
boolean readOnly() default false;
//执行触发回滚的异常类型
Class<? extends Throwable>[] rollbackFor() default {};
//和上面一样
String[] rollbackForClassName() default {};
//抛出指定的异常,不会滚事务
Class<? extends Throwable>[] noRollbackFor() default {};
//和上面一样
String[] noRollbackForClassName() default {};
}
优点:
不需要通过编程的方法管理事务,即不要进行事务手动开启,提交及回滚,直接在需要添加事务的方法上使用@Transactional注解即可
缺点:
最细粒度只能是方法级别,无法像编程事务可以作用到代码块级别
事务失效的几种情况:
- 使用在非public方法上会导致事务失效
- 同一个类中,一个无事务的方法内通过this方式调用有事务的方法会导致事务失效
- 异常被捕捉了导致事务失效
5.编程式事务
编程式事务允许开发者在代码中明确地控制事务的开始、提交和回滚。使用Spring的事务管理器,你可以通过TransactionTemplate
或直接使用PlatformTransactionManager
来实现编程式事务管理。
PlatformTransactionManager示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Override
public int insertConfig() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 设置事务的传播机制
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
// 设置事务的隔离级别
def.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_REPEATABLE_READ);
// 开始事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
throw e;
}
return 1;
}
TransactionTemplate示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public int insertConfig2() {
transactionTemplate.execute(status -> {
try {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
return 1;
});
return 1;
}
通过编程式事务可以手动控制事务的范围
6.避免长事务的方式
①不要在整个大方法上添加Transactional注解,将增删改操作放在一个独立的方法,在这个方法上使用Transactional注解,注意注解失效的情况。
②使用编程式事务,在需要的代码中进行事务控制
③事务中避免远程调用,通过建立重试补偿机制,保证数据的一致性
④事务中避免一次处理太多的数据,可以进行分批处理,处理成功直接提交。这种情况出现异常无法对所有的数据进行回滚,需要将异常的数据返回至前端,或者通过钉钉的方式进行提醒,让用户对异常的数据进行二次处理。例如一次导入大批量数据的情况