【Spring】著作阅读笔记——事务传播行为

25 篇文章 0 订阅
16 篇文章 0 订阅

前言

前段时间整理了数据库事务相关的内容,事务传播行为就是程序对事务的编排。事务传播行为在工作上的使用很重要,曾经在线上环境因为开启了大事务并嵌套小事务造成了死锁问题,现借《Spring 揭秘》整理以下具体知识和使用场景。声明式事务已经非常流行了,在声明的注解上即可指定传播行为。

1. 事务为什么会传播?

1.1 DAO模式

DAO模式的出现,从官网摘了几句话,希望能概括DAO模式具体提供了什么思路:

The DAO implements the access mechanism required to work with the data source.
The business component that relies on the DAO uses the simpler interface exposed by the DAO for its clients.

  • DAO层通常直接跟数据源打交道(关系型数据库、LDAP、Redis),通常是定义一个接口并返回Service层所需要的对象。Service层调用DAO,而不是直接调用数据源,很好得简化了Service层的代码编写(用接口进行解耦)

The DAO completely hides the data source implementation details from its clients. Because the interface exposed by the DAO to clients does not change when the underlying data source implementation changes, this pattern allows the DAO to adapt to different storage schemes without affecting its clients or business components. Essentially, the DAO acts as an adapter between the component and the data source.

  • DAO层可以视为不同数据源和Service层的适配器,可以这么理解,底层的数据源是MySQL,想要升级成Redis,那么在不改业务逻辑代码的前提,既可以完成切换,把数据源的访问和操作都放在了DAO层。如果一个系统是支持升级和重构的,DAO层是不可或缺的。

1.2 事务声明在Service层

上文说到,DAO层是负责抽象数据源的操作和访问。事务在概念上是保证业务处理的原子性,业务处理是一至多个的数据库操作,所以从三个角度来看,事务都需要声明在Service层

  1. DAO要保证切换数据源的灵活性,并不是所有数据库引擎都支持事务
  2. Service层表示业务逻辑层,业务逻辑需要原子性,需要事务保护
  3. Service层会出现Service的嵌套使用,每个Service上声明事务也更利于定义事务传播行为

2. 事务传播行为的分类

service A -> service B 作为一个调用关系,事务传播行为有以下分类

2.1 Spring 默认:PROPAGATION_REQUIRED

REQUIRED 必要的。意思是被声明的业务逻辑必须处于一个事务中, 如果service A不在事务中,service B会创建一个事务让自己处于事务控制中。

2.2 甘愿被控制 :PROPAGATION_SUPPORTS

SUPPORTS支持。意思是调用方需要什么,自己就成为什么。如果service A 在事务中,service B就加入A的事务,反之不使用事务。

这种情况很适合查询语句,如果调用方是个更新语句并且声明为事务,且期望查询的数据是当前事务可见的最新版本,那么查询需要加入到事务中。如果只是单独调用查询,并不需要开启事务。( RC隔离级别下除外)

2.3 受虐狂:PROPAGATION_MANDATORY

MANDATORY强制的。强制要求service B处于一个事务中,即service A一定要在事务中调用service B否则抛出异常,但是service B又不管理事务的提交和回滚。service B享受被service A事务操控,没有还不行。

2.4 特立独行: PROPAGATION_REQUIRES_NEW

REQUIRES NEW 需要一个新的。不管service A是否存在事务,service B都会新建一个事务给自己用。service A如果存在事务,service A自身会被挂起。

  • 挂起(suspend)状态是保持阻塞,如果事务持有锁,该锁会一直不放弃,造成死锁隐患。
  • 特别地,service A 和 service B的事务是独立的,如果存在锁竞争,则更容易造成死锁。

2.5 被包养: PROPAGATION_NESTED

NESTED 嵌套。service A 和 service B属于父子事务关系,子事务抛出异常不捕获,父子事务同时回滚。如果父事务捕捉了子事务的异常,还可以调整异常策略使得父事务不会滚。对比于特立独行的REQUIRES_NEWNESTED 事务直接存在依赖关系。

2.6 其他:PROPAGATION_NOT_SUPPORTED / PROPAGATION_NEVER

不使用事务,有微小应用差距,目前还看不到使用场景。如果不想开启事务,不声明即可。

3. 线上事故回顾

3.1 需求

爬取数据并更新进数据库,并同步物流节点数据(与订单表有关联关系)。

3.2 出错场景

爬虫应用的service
查询耗时: 15s
更新耗时: 1s
一年前写了个爬虫,把声明式事务加在了方法的最外层,导致锁等待超时触发异常回滚。按理说,尽管在事务中查询也不会锁住表,后面查询和更新是交替进行的,也就是第一条更新语句出现在前1s,随着循环增加,update锁住的行数据就更多,直到出现死锁。

3.3 解决办法

  1. 原方法拆成两个service , 查询service不加事务,更新service待优化。
  2. 更新事务的语句合并多条 where xxxx = ‘’; where xxx = ‘’; (拼接成一大串含有分号的脚本再统一提交, mybatis-plus可以实现),保证数据量多的情况都能命中索引,防止锁表。
  3. 每30条update语句就进行数据库连接,该业务逻辑设为service c,把PROPAGATION_REQUIRES_NEW声明在service c 上。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值