1.什么是事务
事务(transaction)是访问并可能更新数据库中各数据项的一个程序执行单元。由事务开始到事务结束之间的全部操作组成。
2.事务的四个特性
事务具有四个特性,原子性、一致性、隔离性和持久性。通常成为ACID。
原子性:是指事务是一个不可分隔的操作单元,无论是本地事务或是分布式事务,均要求在一个事务内的所有操作,要么一起成功,要么一起失败,不允许存在部分成功,部分失败的情况。如果执行过程中发生失败,则需要回滚到事务开始前的状态。
一致性:执行一次事务,要求各个数据项从一个正确的状态转换到另一个正确的状态,要求执行前后的数据是完整的。
隔离性:在一次事务的执行过程中,要求事务锁定的数据操作只存在于该事务中,不影响其他事务的操作,各个事务之间的操作是相互隔离的。只有当前事务提交后,其他事务才能看到当前事务更新的数据。
持久性:事务执行完成后,会对数据做持久化存储,即使发生宕机等情况,数据依旧存在。
3.事务的隔离级别
3.1 隔离级别所解决问题(脏读、幻读、不可重复读)
脏读:A事务读取到了B事务尚未提交的数据(尚未提交的数据可能发生回滚)。
幻读:范围读取,在同一个事务内相同的条件(age > 10)多次读取到的数据量不一致。
不可重复读:同一个事务内,相同的条件(age = 10)多次读取的数据结果不一致。
3.2 事务的隔离级别
未提交读(Read Uncommited):A事务可读取到B事务尚未提交的数据,可导致脏读、幻读、不可重复读。
已提交读(Read commited):A事务可读取B事务已经提交的数据。可能存在A事务执行过程中B事务已经修改完成,并提交了数据。可导致幻读。不可重复读。
可重复读(Repeated Read):可重复读指在同一个事务内,锁定当前事务操作的数据,在这个事务还没有结束之前,不允许其他的事务操作这些数据,避免了脏读,不可重复读。但是仍存在幻读的可能。
可串行化(Serializanle):保证各个事务顺序执行,事务之间互斥,最高的隔离级别,锁表操作。避免了脏读、幻读、不可重复读,但是并发效率低下。
4.本地事务
借助关系型数据库来完成事务,关系数据库通常有ACID特性,传统的单体应用通常会将数据存储在一个数据库中,会借助数据库完成事务。
5.分布式事务
5.1 CAP理论
CAP理论是分布式处理的理论基础,分布式系统在设计时只能在一致性、可用性、分区容忍性之中满足其中的两项,无法同时满足。
一致性(Consistency):节点A、B、C都存储了用户数据,要求同一时刻三个节点的数据需要保持一致性。
可用性(Availiability):服务部署A、B、C三个节点,其中一个节点宕机不会影响系统对外提供服务,如果只有一个A节点,当A节点宕机会导致整个系统无法对外提供服务,增加服务节点B、C是为了保证系统的可用性。
分区容忍性(PartitionTolerance):分区容忍性就是允许系统之间通过网络协同工作,分区容忍性要解决由于网络分区导致的数据不完整及无法访问的问题。
CAP组合方式:CA、CP、AP
CA:放弃分区容忍性,保证数据的可用性和强一致性,关系型数据库按照CA设计。
CP:放弃可用性,加强一致性和分区容忍性,一些强一致性需求要求系统按照CP设计。比如跨行转行,一次转账请求需要等待双方银行系统都完成,整个事务才算完成。需要说明:由于网络问题,该设计可能存在性能问题,导致出现超时等待的情况,如果没有及时处理超时问题,则可能导致整个系统出现阻塞情况。
AP:放弃强一致性,保证数据最终的一致性。加强可用性和分区容忍性,很多NOSQL数据库是按照AP设计的。
总结:在分布式系统中,AP的应用是比较多的,即保证系统的可用性和分区容忍性,牺牲数据的强一致性(写操作后立即读取新数据),保证数据的最终一致性。比如:订单退款、今日退款成功、明日到账等等。
5.2 分布式事务一致性解决方案
5.2.1 两阶段提交协议2PC
两阶段提交由协调者和参与者组成,共经过两个阶段,三个操作,部分关系型数据库支持两阶段提交协议。
1)第一阶段:准备阶段,协调者通知参与者准备提交订单,参与者开始投票;参与者完成操作后向协调者返回操作结果YES|NO;
2)提交commit或回滚rollback阶段,协调者根据参与者返回的结果,想参与者发送提交或回滚的指令。
例如下单减库存(图是@村口张大爷的)
操作如下:
1)应用程序链接两个数据源;
2)应用事务协调器向两个库发起prepare,两个数据库分别执行本地事务(记录日志)但是不提交,如果执行结束则根据执行结果分别向协调器返回YES|NO。
3)协调器收到参与者返回的指令,只要其中一个参与者返回NO的指令,则向所有参与者发送回滚的执行,参与者回滚事务。
4)事务协调器收到YES指令,则向所有参与者发送提交指令,如果参与者有一方提交失败,则由事务协调器发起回滚事务。
2PC的优点,支持事务的强一致性,部分关系型数据库支持(MYSQL、SQLSERVER等)
缺点:整个事务需要协调者在多个参与者节点之间协调,执行时间长,性能低下。
实现方式:SpringBoot+Atomikos OR Bitronix
5.2.2 事务补偿TCC
TCC事务补偿是基于2PC实现的业务层事务控制方案,Try\Confirm\Cancel
Try:检查并预留资源,完成事务执行前的检查,并预留事务执行所需要的资源;
Confirm:确定执行业务操作,对Try阶段预留的资源正式执行;
Cancel:确定取消业务操作,对Try阶段预留的资源执行释放;
例如下单减库存(图是@村口张大爷的)
操作如下:
Try:
1)下单业务由订单系统和库存系统协调完成,Try阶段对订单服务和库存服务进行检查,并预留事务执行所需的资源。
2)检查订单服务是否满足下单的条件(比如是否存在未提交的订单)。
3)检查库存服务是否满足下单的条件(比如库存是否充足)。
Confirm:
1)订单服务和库存服务在完成Try阶段后,正式开始执行业务操作。
2)订单服务向订单库中写入下单记录;
3)库存服务减去库存。
Cancel:
1)订单服务或是库存服务一方出现失败则全部取消操作。
2)订单服务需要删除新增的记录;
3)库存服务需要恢复库存。
TCC优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。
缺点:开发成本高,每个事务操作都需要参与者实现Try/Confirm/Cancel三个接口。
注意:Try/Confirm/Cancel三个接口都需要实现幂等性,在接口执行失败后不断重试。
幂等性,是指同一个操作无论执行多少次,返回的结果都是一致的。
实现方法:
1、操作之前在方法中进行判断,执行过了就不再重复执行了。
2、缓存所有请求和执行结果,已经处理的请求直接返回结果。
3、在数据表中加一个字段,数据操作时判断字段状态再处理。
5.2.3 消息队列实现最终一致性
消息队列实现将一个事务拆分成多个本地事务来完成,并且由消息队列异步协调完成。
例如下单件库存(图是@村口张大爷的)
6.Spring事务的传播机制
6.1 事务在Spring中的运作方式
执行过程:开始事务(AOP织入)-@Transaction修饰的方法-处理事务提交或回滚。
Transaction注解可以修饰方法也可以修饰类,修饰类的时候,默认对该类下符合条件的方法均织入事务。
1、开启事务:获取数据库连接,关闭自动提交。
2、异常回滚:默认情况下只有RuntimeException和Error会进行回滚的,因此回滚前会检查rollBackOn的关键字,查询注解是否做了显示的声明。
6.2 Spring事务传播机制
Required:支持当前事务,如果不存在,则新建一个事务执行。
Supports:支持当前事务,如果不存在,则以非事务方式执行。
Mandatory:强制的,支持当前事务,如果不存在,则抛出异常。
Required_New:不支持当前事务,创建一个新事务并挂起当前事务。
Not_Supports:以非事务方式执行,如果存在事务,则挂起,以非事务方式执行。
Never:以非事务方式执行,如果存在事务,则抛出异常。
Nested:如果存在当前事务,则嵌套事务执行,不存在则执行Required操作。
在深入讨论 @Transactional 之前,我们需要先了解什么是事务。在关系型数据库中,事务是一组数据库操作,它们被视为单个逻辑单元并必须一起执行或一起回滚。这意味着如果其中一个操作失败,整个事务都将被回滚,以确保数据库保持一致状态。例如,如果您要将一笔资金从一个账户转移到另一个账户,则必须在一个事务中执行两个操作,以确保不会出现错误情况,如余额不足等。
Spring 框架提供了 @Transactional 注解,使得我们能够在方法级别上定义事务边界。在使用 @Transactional 注解时,Spring 将自动创建一个事务,并将该方法包含在该事务中。如果该方法正常执行,则事务将被提交。如果方法抛出异常,则事务将被回滚。这使得我们能够更轻松地管理事务,并将关注点集中在业务逻辑上。
现在我们已经了解了事务和 @Transactional,让我们深入了解 @Transactional 的工作原理。在 Spring 中,@Transactional 是一个基于 AOP 的注解,它使用 Spring 的事务管理器来管理事务。
当您在方法上使用 @Transactional 注解时,Spring 将会创建一个代理对象来包装该方法。该代理对象将在该方法执行之前和之后添加一些代码,以启动和提交/回滚事务。
代理对象的创建和配置是由 Spring 的事务管理器完成的。Spring 支持多种事务管理器,例如 JDBC 事务管理器、Hibernate 事务管理器和 JTA 事务管理器。这些事务管理器负责管理事务的生命周期,并确保事务的一致性和隔离性。
在执行带有 @Transactional 注解的方法时,代理对象将首先尝试获取一个数据库连接。如果该方法已经在另一个事务中执行,则代理对象将重用该连接。否则,代理对象将从事务管理器中获取一个新的连接。然后,代理对象将启动事务,并将该连接与该事务相关联。
一旦方法执行完成,代理对象将检查方法是否抛出了异常。如果没有异常,则代理对象将提交事务,并将连接释放回事务管理器。如果方法抛出异常,则代理对象将回滚事务,并将连接释放回事务管理器。
需要注意的是,@Transactional 的默认行为是将 RuntimeException 和 Error 视为需要回滚事务的异常。这意味着如果您的方法抛出这些异常之一,则事务将被回滚。如果您想将其他异常也视为需要回滚事务的异常,则可以通过在 @Transactional 注解中添加 rollbackFor 属性来指定。
虽然 @Transactional 很方便,但也有一些需要注意的坑。下面是一些常见的问题和解决方案。
坑1:事务传播机制
Spring 提供了多种事务传播机制,用于定义在方法调用期间如何处理事务。默认情况下,事务传播级别为 REQUIRED。这意味着如果方法调用已经在一个事务中,则该方法将加入该事务。否则,将创建一个新的事务。
在使用 @Transactional 注解时,必须注意事务传播机制。如果您的方法调用了另一个带有 @Transactional 注解的方法,则该方法将在相同的事务中执行。如果您不希望发生这种情况,则可以使用事务传播级别来控制事务的行为。
坑2:懒加载
Hibernate 等 ORM 框架支持懒加载。这意味着在查询数据库时,只有当需要时才会加载相关的实体。如果您使用 @Transactional 注解,则会在事务结束时关闭数据库连接。这意味着如果您在事务外部访问了一个懒加载的实体,则会抛出 LazyInitializationException 异常。
要解决这个问题,您可以使用 Open Session in View 模式,该模式允许您在视图渲染时保持 Hibernate 会话处于打开状态。另一种解决方案是使用 fetch = FetchType.EAGER 注释来强制 ORM 框架在查询时立即加载实体。
坑3:异常处理
当方法抛出异常时,@Transactional 注解将会回滚事务。但是,有时您可能希望在处理异常后继续执行事务。例如,如果您正在处理一个无关紧要的异常,您可能不想回滚事务。
要解决这个问题,您可以在方法内部手动捕获异常并继续执行事务。您还可以使用 @Transactional 注解的 noRollbackFor 属性来指定不需要回滚的异常类型。
事务管理对于现代应用程序非常重要。Spring 框架提供了 @Transactional 注解来帮助我们更轻松地管理事务。@Transactional 的工作原理是基于 AOP 的代理对象。在执行带有 @Transactional 注解的方法时,代理对象将启动事务并确保其一致性和原子性。然而,@Transactional 也有一些需要注意的坑,如事务传播机制、懒加载和异常处理。了解这些问题并采取适当的解决方案可以帮助您更有效地使用 @Transactional 注解来管理事务。
在使用 @Transactional 注解时,您应该牢记以下几点:
理解事务的工作原理:@Transactional 注解是基于 AOP 的代理对象,它可以确保事务的一致性和原子性。
注意事务传播机制:默认情况下,@Transactional 注解的事务传播级别为 REQUIRED。这意味着如果方法调用已经在一个事务中,则该方法将加入该事务。否则,将创建一个新的事务。
处理懒加载:如果您正在使用懒加载实体,则必须注意在事务结束时关闭数据库连接的问题。您可以使用 Open Session in View 模式或强制立即加载实体来解决这个问题。
处理异常:@Transactional 注解会在方法抛出异常时回滚事务。但是,您可能希望在处理异常后继续执行事务。您可以手动捕获异常并继续执行事务,或使用 noRollbackFor 属性来指定不需要回滚的异常类型。
在使用 @Transactional 注解时,还有一些其他的注意事项,如事务超时、事务隔离级别等。了解这些问题并采取适当的解决方案可以帮助您更好地管理事务。
总之,@Transactional 注解是 Spring 框架中非常强大和方便的特性,可以帮助我们更好地管理事务。但是,了解其原理和注意事项是非常重要的。希望本文能够帮助您更好地理解 @Transactional 注解,并在实际项目中更好地应用它。