分布式专题-1、Spring事务(本地事务)
1. 本地事务
商品新增功能非常复杂,商品管理微服务在 service 层中调用保存 spu 和 sku 相关的方法,为了保证数据的一致性,必然会使用事务。
1.1. 基本概念
事务的概念:事务是逻辑上一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
事务的四个特性(ACID):
- 原子性 (atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
- 一致性 (consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证 事务执行之前 数据是正确的,事务执行之后 数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
- 隔离性 (isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在 并发执行过程中不会互相干扰
- 持久性 (durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到 持久化存储器 中。
1.1.1. 相关命令
查看全局事务隔离级别:
1 |
|
设置全局事务隔离级别:
1 |
|
查看当前会话事务隔离级别:
1 |
|
设置当前会话事务隔离级别:
1 |
|
查看 mysql 默认自动提交状态:
1 |
|
设置 mysql 默认自动提交状态:
1 |
|
开启一个事务:
1 |
|
提交事务:
1 |
|
回滚事务:
1 |
|
在事务中创建一个保存点:
1 |
|
回滚到保存点:
1 |
|
1.1.2. 事务实现原理
MySQL-2、事务-MVCC-LBCC1.2. 隔离级别
❕ ^95ef3l
事务隔离级别,是为了解决多个并行事务竞争导致的数据安全问题的一种规范。MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。对于整个数据库来说是并行事务,对于确定的共同操作的记录来说是并发的。
并发写:使用 mysql 默认的锁机制(独占锁)
并发读产生问题:
- 脏读: 一个事务可以读取另一个事务未提交的数据
- 不可重复读 :一个事务可以读取另一个事务已提交的数据单条记录前后不匹配
即在当前事务中读到了其他事物 commit 的更新操作
**在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。**
- 虚读(幻读): 一个事务可以读取另一个事务已提交的数据读取的数据前后多了点或者少了点。即在当前事务中多次读,读到了其他事物 commit 的插入或删除操作,导致结果集记录条数出现变化
**在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。**
解决读问题:设置事务隔离级别
- read uncommitted(0)
- read committed(2)
- repeatable read(4)
- Serializable(8)
隔离级别越高,性能越低。
一般情况下:脏读是不可允许的,不可重复读和幻读是可以被适当允许的。
1.2.1. Spring 事务管理
跟数据库的一致,但比较霸道,如果数据库跟 spring 中配置的不一样,以 spring 配置为准
1.2.2. 隔离级别实现原理
MySQL-2、事务-MVCC-LBCC1.3. 传播行为
事务的传播行为不是 jdbc 规范中的定义。传播行为主要针对实际开发中的问题
1.3.1. 死活不要事务的-NEVER、NOT_SUPPORTED
NEVER: 直接报异常,不存在回滚不回滚的辩论问题了
NOT_SUPPORTED: 外层有事务会被内层方法挂起,内层方法以非事务运行,不存在回滚,外层捕获内层的异常的话,可以达到外层回滚,但内层没事务,所以不受控制
1.3.2. 可有可无事务的-SUPPORTS
SUPPORTS:有就用外层的,跟外层一体,没有就不用,非事务运行
1.3.3. 必须要有事务的
1.3.3.1. 只会有 1 个事务-同进退
1.3.3.1.1. REQUIRED-默认
Spring 默认的传播方式,外层有就加入外层事务,如果外层没有则新建事务。内外层,不管哪层有异常,都一起回滚
外层异常,外层可以控制内层一起回滚 (就算内层没有异常);而内层异常,由于设置了回滚标记,外层捕获异常后,内外层都要回滚。
1.3.3.1.2. MANDATORY
强制性用外层的,如果没有就抛出异常,如果有就用外层事务。内外层,不管哪层有异常,都一起回滚
1.3.3.2. 必会新建 1 个事务
1.3.3.2.1. REQUIRES_NEW-互不影响
存在 (1 个 or 2 个事务):==不管外层有没有,都会新建一个事务==,如果外层有,则将原来的先挂起,直到新的事务完成。 而且回滚动作 2 个事务互不相关
它通常用于处理需要独立于其他事务的操作,例如记录日志或者在遇到异常时回滚该操作。外层和内层互不影响,完全独立的 2 个事务
使用场景
常用于日志记录, 或者交易失败仍需要留痕.
还有就是 时序控制, 支付依赖于已经创建的订单, 无订单不支付. 先要有订单才能支付. 常见于事务步骤 要求时序 的情况。比如一键购、再来一单这种业务场景
1.3.3.2.2. NESTED-外管内
存在 (1 个 or 2 个事务):==不管外层有没有,都会新建一个事务==,如果外层有,则新建一个事务嵌套在外层事务中,外层回滚控制内层一起回滚,内层回滚外层可以正常提交,因为内层事务把回滚标记清除了,外层事务看不到回滚标记,就只正常提交了外层自己的事务:外层可以控制内层一起回滚,内层回滚无法连同外层回滚
1.3.4. 业务案例
❕
事务-1、Spring事务(本地事务)1.3.4.1. REQUIRES_NEW
开启一个全新的事务, 一般用于分布式事务中的一个前期步骤. 比如一键购功能. 主要步骤包括:
- 创建订单
- 创建订单
- 扣除库存
- 创建物流订单
- 发起支付
- 对于支付,清算相关的业务流程可以参考: 深度解析:什么是支付核心?
下面的伪代码模拟了一个外层事务和内层事务的业务过程.
1 |
|
OrderService.createOrder 运行在外层事务中, 如果创建订单失败, 就没必要发起支付了, 直接回滚了, 根本就到不了支付这一步.
当绑定的银行卡余额不足的情况, PayService.pay(); 是可以回滚的, 而不会影响 buyDirectly 整个事务, OrderService.createOrder(); 成功. 向银行卡不足金额后, 可以重新发起支付, 完成购买过程.
注意: Propagation.REQUIRES_NEW 如果作为一个子事务运行, 调用者和被调这不要在同一个服务类中 (因为 Spring AOP 动态代理的限制, 在同一个类中事务是不起作用的)
Propagation.REQUIRES_NEW 的一般使用场景是作为内层事务可以单独回滚. 而不是回滚整个外层事务. 因此如果调用者和被调用者如果在一个类中, Propagation.REQUIRES_NEW 注解的方法并 不会 开启一个新的事务. 因此就达不到内层事务单独回滚的目的.
归纳: 内层事务可以独立回滚, 不影响外层事务.
前提是外层事务的方法不能和内层事务的方法在同一个服务类中
❕
1.3.4.2. NESTED
未使用
1.4. 回滚策略
分布式专题-3、事务失效1.5. 事务超时
https://dongzl.github.io/2020/08/04/33-Spring-Transaction-Timeout/index.html
1.5.1. 分析总结
分析了使用 JdbcTemplate
和 MyBatis
框架的不同执行过程,虽然代码实现不同,但是最终的效果应该说是一致的。
在一个事务当中如果执行多条 SQL
,每次执行创建 Statement
对象时都会检查是否已经出现超时,如果未超时,则会设置 Statement
的 queryTimeout
属性,继续执行 SQL
,如果本次执行一切 OK,则在执行下一条 SQL
语句时会重复上面逻辑,如果所有 SQL
语句全部执行成功,即使在最后设置一个耗时操作,也不会出现事务超时的,耗时操作并不会计入事务的超时时间判断;当然,如果某个耗时操作执行完后,还有 SQL
语句需要执行,那么这个耗时操作的时间是会计入到事务的超时时间当中的。
在上面的博客文章中,作者总结了一个公式:
Spring 事务超时 = 事务开始时到最后一个 Statement 创建时时间 + 最后一个 Statement 的执行时超时时间(即其 queryTimeout)
1.6. 只读事务
如果一个方法标记为 readOnly=true 事务,则代表该方法只能查询,不能增删改。readOnly 默认为 false
2. 什么是 Spring 事务⭐️🔴
❕ ^9ocq1k
2.1. 与数据库事务关系
之前一直觉得事务只针对于数据库当中,5 种隔离级别,7 种传播行为,后来才发现这是针对 Spring 的,对数据库来说隔离级别只有 4 种,Spring 多了一个 DEFAULT 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别.
总的来说,本质上其实是同一个概念
spring 的事务是对数据库的事务的封装, 最后本质的实现还是在数据库, 假如数据库不支持事务的话,spring 的事务是没有作用的
数据库的事务说简单就只有开启, 回滚和关闭,spring 对数据库事务的包装, 原理就是拿一个数据连接, 根据 spring 的事务配置, 操作这个数据连接对数据库进行事务开启, 回滚或关闭操作. 但是 spring 除了实现这些, 还配合 spring 的传播行为对事务进行了更广泛的管理. 其实这里还有个重要的点, 那就是事务中涉及的隔离级别, 以及 spring 如何对数据库的隔离级别进行封装. 事务与隔离级别放在一起理解会更好些.
2.2. 使用方法
springboot 2.x 可直接使用@Transactional 玩事务,传播行为默认是 REQUIRED
2.3. 方法级别事务
同一个类中方法调用,想要设置事务生效,必须使用代理
2.4. 本地事务局限
2.5. 声明式事务
Spring-5、声明式事务-@EnableTransactionManagement2.6. 实现原理
2.6.1. 传播行为实现原理
面试专题-2、Spring-事务3. 实战经验
4. 参考与感谢
【小家java】Spring事务不生效的原因大解读
Spring注解@Async和@Transactional失效问题
/Users/weileluo/001-study/shangguigu/谷粒商城全套视频/分布式高级篇/283、商城业务 - 分布式事务 - 本地事务在分布式下的问题
业务场景: https://segmentfault.com/a/1190000015794446 ^bnb0h5
事务,事务隔离级别,spring 事务配置,spring 事务的传播特性 - 阿里巴巴面试题,面试题系列(十)