事务基本介绍
MySQL事务是访问并更新数据库中各项数据项的一个程序执行单元。在事务中的所有操作,要么都执行修改,要么都不执行,这是事务的目的,也是事务模型区别于文件系统的重要特征之一。
事务具有四个特性,也被称为ACID特性:
a. 原子性(Atomicity):事务是一个不可分割的逻辑工作单元,其中的操作要么全部执行成功,要么完全不执行。如果事务中的一个操作失败,则整体回到事务开始前的状态。
b. 一致性(Consistency):事务必须使数据库从一个一致性状态转移到另一个一致性状态。一致性的含义是指数据库中的数据必须满足所有的完整性约束。
c. 隔离性(Isolation):一个事务的执行不能被其它事务操作影响。即事务内部的操作及其影响对其它并发事务是隔离的,并发执行的事务之间互不影响。
d. 持久性(Durability):一旦事务提交,则其所做的更改将会永久保存在数据库中。即使发生系统故障,这些更改也不会丢失。
事务分类
事务可以分为不同类型:
- 扁平事务:所有操作处于同一层次,最常见的方式是BEGIN WORK,操作1,操作2,…,操作N,COMMIT WORK。
- 带有保存点的扁平事务:除了支持扁平事务的操作外,允许在事务执行过程中回滚到较早的一个保存点。
- 链事务:在提交一个事务时,释放不需要的数据对象,并将必要的处理上下文传递给下一个事务。提交事务操作和下一个事务操作合并为一个原子操作。
- **嵌套事务:**在事务中嵌套事务,形成类似树的结构,顶层事务是根节点,子事务可以提交或回滚,但只有在顶层事务提交后才真正生效。
- 分布式事务:在分布式环境下运行的扁平事务,需要通过网络访问不同节点执行不同的事务。
事务的使用可以保证数据的一致性和完整性,特别适用于处理操作量大、复杂度高的数据。
MySQL事务相关内容
在MySQL中,事务通常用在支持事务的存储引擎,如InnoDB和NDB(MySQL Cluster)中。MyISAM这样的存储引擎不支持事务。
在MySQL中,可以通过以下语句来控制事务的开始和结束:
○ START TRANSACTION 或 BEGIN:开始一个新的事务;
○ COMMIT:提交一个事务;
○ ROLLBACK:回滚一个事务。
事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。MySQL支持以下隔离级别:
读未提交(READ UNCOMMITED): 允许事务读取未被其它事务提交的更改。
可能造成的问题:脏读。事务可以读取到其他事务中未提交的数据,可能会读取到无效或错误的数据。
读已提交(READ COMMITED) :确保事务只能读取已经被其它事务提交的更改。
可能造成的问题:不可重复读。在同一个事务中,同一个查询在不同的时间得到了不同的结果,因为其他事务可能在事务执行期间提交了数据。。
可重复读(REPEATABLE READ) :确保事务内可以多次读取同样的数据而不会看到其它事务所做的更改。这个MySQL的默认隔离级别。
可能造成的问题:幻读。在同一个事务的不同时间使用相同的查询,可能会得到不同的结果,因为其他事务可能在事务执行期间插入或删除了数据。
幻读演示:
#前提:name字段不能有相同value
#A窗口
START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
#B窗口
START TRANSACTION;
SELECT * from account;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
#操作步骤:
#1、先执行B窗口前两条数据,查询结果中并没有 zhangsan 的账号;
#2、执行A窗口的所有命令(在可重复读隔离级别中,A窗口提交的事务,B窗口还是查不到的);
#3、执行B窗口的第三行命令,报错,因为 name 重复。此时,就像产生了幻觉,明明没有 zhangsan 用户,但是插入提示名字重复,导致失败。
串行化(SERIALIZABLE):这是最高的隔离级别,它通过锁定涉及的每一行来防止其他事务看到当前事务的操作。
Spring中的事务
在Spring框架中,事务管理是一个核心的部分,它提供了一种抽象层来控制事务的编程和声明。Spring的事务管理支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理
编程式事务管理意味着你需要在代码中显式地管理事务的生命周期,包括开始事务、提交事务和在出现异常时回滚事务。这通常通过使用 TransactionTemplate 或直接使用 PlatformTransactionManager 来完成。虽然这种方式提供了最大的控制,但它也使得代码与事务管理紧密耦合,并且导致了大量的模板代码。
@Resource
privated TransactionTemplate transactionTemplate;
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@NotNull TransactionStatus transactionStatus) {
try {
// 执行事务操作
saveData();
// 完成后手动提交事务
transactionStatus.flush();
} catch (Exception ex) {
// 出错时手动回滚事务
log.error("请求参数:{},相应结果:{}", body, req.getText());
transactionStatus.setRollbackOnly();
}
}
});
声明式事务管理
声明式事务管理是通过Spring的AOP(面向切面编程)实现的,它允许你通过配置来管理事务,这样业务代码就可以与事务管理分离,减少了代码的侵入性。在Spring中,声明式事务管理通常是通过@Transactional注解来实现的。
@Transactional 注解
@Transactional注解可以被应用到接口定义、类定义、类的public方法上。当一个类或方法被@Transactional注解修饰后,Spring框架会在这个类/方法被调用时自动开始一个事务。如果方法正常完成,事务会被提交,如果方法抛出异常,事务会被回滚。
@Transactional注解中可以设置多个参数来定制事务的行为,如:
● propagation:事务的传播行为
● isolation:事务的隔离级别
● timeout:事务的超时时间(默认-1)
● readOnly:标记事务是否为只读
● rollbackFor:定义哪些异常需要触发事务回滚
● noRollbackFor:定义哪些异常不触发事务回滚
事务的传播行为
Spring定义了多种事务传播行为,以下是几个常用的:
● PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
● PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
● PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起当前事务(使用MyBatis PLUS多数据源时,可以用于不同数据源维护事务)。
● PROPAGATION_NEVER:如果当前存在事务,则抛出异常。
● PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行(使用一个单独的保存点)。
传播行为区别
PROPAGATION_REQUIRED和PROPAGATION_NESTED的区别主要体现在以下几个方面:
- 开启事务的多少:PROPAGATION_REQUIRED会在当前事务中执行,而PROPAGATION_NESTED是在嵌套事务中执行。
- 事务的回滚:PROPAGATION_NESTED的回滚可以选择性地回滚嵌套事务,而不会影响父事务的提交或回滚;而PROPAGATION_REQUIRED是整个事务一起回滚或提交。
- 事务的独立性:PROPAGATION_NESTED是嵌套在父事务中的子事务,它们共享相同的事务资源;而PROPAGATION_REQUIRED是独立的事务。
事务的隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度。常见的隔离级别包括:
● ISOLATION_DEFAULT(默认):使用底层数据库的默认隔离级别。
● ISOLATION_READ_UNCOMMITTED:允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。
● ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以防止脏读,但是幻读或不可重复读仍然可能发生。
● ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本事务自己所修改的,可以防止脏读和不可重复读,但幻读可能发生。
● ISOLATION_SERIALIZABLE:完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻读。
事务管理器
Spring提供了多种事务管理器来支持不同类型的数据访问资源。最常用的事务管理器包括:
● DataSourceTransactionManager:用于JDBC的事务管理。(引入了 spring-boot-starter-data-jdbc 启动器依赖会自动配置)
● HibernateTransactionManager:用于Hibernate会话的事务管理。
● JpaTransactionManager:用于JPA实体管理器的事务管理。
● JtaTransactionManager:用于全局事务(如分布式事务)的管理。
事务管理器是Spring事务抽象的核心,它与Spring的事务策略和配置紧密结合,为不同的持久化框架提供了一致的编程模型。
事务同步
Spring事务管理还涉及到事务同步机制,这是一个框架级的特性,用于确保资源(如数据库连接)与当前事务适当地绑定和清理。事务同步对于开发者来说是透明的,由Spring框架自动处理。
事务异常和回滚
在声明式事务管理中,Spring默认对所有运行时异常(RuntimeException和Error)进行回滚,但不对检查型异常(Exception的其他子类)进行回滚。你可以通过@Transactional注解的rollbackFor和noRollbackFor属性来定制哪些异常应该触发回滚。
Spring事务失效
- 数据库引擎不支持事务:某些数据库引擎(如MySQL的MyISAM引擎)不支持事务操作,因此无论如何设置,事务都无法生效。
- 方法没有被Spring管理:如果一个类没有被Spring注解(如@Service)标记为Bean,那么该类的方法上的@Transactional注解将不会生效,因为Spring无法对该类进行代理。
- 方法不是public的:@Transactional注解只能用于public方法上,如果将其用于非public方法,可以考虑使用基于AspectJ框架的静态代理模式。
- 发生自身调用:如果一个方法发生自身调用,即调用了同一个类中的另一个方法,而没有经过Spring的代理类,事务将不会生效。解决方案之一是在事务所在的类中注入自己,并通过代理类调用方法。
- 没有配置事务管理器:如果没有配置数据源事务管理器(如DataSourceTransactionManager),事务也不会生效。在Spring Boot中,引入了spring-boot-starter-data-jdbc依赖后会自动配置数据源事务管理器,但在传统的Spring框架中需要手动配置。
- 设置了不支持事务:如果在一个方法上设置了@Transactional(propagation = Propagation.NOT_SUPPORTED),表示该方法不以事务方式运行,当前若存在事务则挂起,这将导致事务不生效。
- 异常没有被抛出:如果在一个事务方法中捕获了异常但没有抛出,事务将不会回滚。只有捕捉到异常并抛出时,事务才会生效。
- 异常类型不匹配:Spring默认只回滚RuntimeException异常,如果抛出的异常类型不匹配,默认的事务回滚机制将不会生效。可以通过在@Transactional注解上指定异常类来触发其他异常的回滚。
参考链接
1、事务隔离级别
2、Spring事务失效