Spring Boot 大事务优化
所谓大事务,就是耗时较长的事务。
Spring 有两种方式实现事务,分别是声明式事务和编程式事务,如果不开启事务,MySQL 默认自定提交事务,也就是语句执行完后自动提交。
大事务产生的场景
- 操作的数据量大,或者事务中包含慢查询。
- 在事务中调用了外部 HTTP接口 或 RPC 服务。
- 大量的锁竞争。
- 执行了比较耗时的计算。
大事务造成的影响
- 大并发情况下,数据库连接池容易挤满。
- 锁定太多的数据,造成大量的阻塞和锁超时,导致数据库性能下降。
- 执行时间长,容易造成主从同步延迟时间长。
- 发生异常,回滚所需的时间比较长。
- undo log 日志膨胀,不仅增加了存储空间,而且可能降低查询的性能。
优化方式
使用编程式事务
在实际项目开发中,我们通常使用声明式事务,直接在方法上使用 @Transactional
注解。
1 |
|
但是声明式事务有以下两个问题:
@Transactional
注解是通过 AOP 实现的,但是如果使用不当,事务可能会失效,如果经验不丰富的话,很难排查问题。- 直接加在方法上,粒度太粗,不好控制事务范围,很容易造成大事务。
所以,我们可以使用编程式事务,通过 TransactionTemplate
类来手动实现事务控制。
代码如下:
以下使用的是带有返回值的 TransactionCallback
,如果你不需要返回值,则可以使用 TransactionCallbackWithoutResult
。
1 | CreateNewUserResult createNewUserResult = transactionTemplate.execute(new TransactionCallback < CreateNewUserResult > () { |
如果是业务简单的代码,直接使用 @Taansactional 也是可以的,但是要注意事务失效问题。
将 select 语句放到事务外
在事务中执行 select 语句,会导致事务中包含慢查询,从而导致大事务。我们可以将 select 语法的方法放到事务外,查询相关操作是不需要事务的。
1 | Member member = memberMapper.selectByPhone(vo.getMobile()) |
避免请求外部HTTP接口或RPC服务
在业务中调用系统的接口是不可避免的,因为网络因素,可能接口响应时间较长,如果放在事务中,很可能就造成大事务了。
避免一次性处理太多数据
比如为了操作方便,可能会一次批量更新1000条数据,这样会导致大量数据锁等待,在并发大的时候尤为明显,解决办法是分页处理,比如前端每次请求只处理 50 条数据,这样可以大大减少大事务的出现。
异步处理
很好理解,减少事务的执行时间。
@Transactional 失效场景
- @Transactional 应用在非 public 方法上。
- @Transactional 注解属性
rollbackFor
设置错误,也就是异常无法兼容,Spring 默认抛出unchecked
异常(继承自RuntimeException
)或者Error
才会回滚事务,其它异常不会触发事务回滚,需要自己指定rollbackFor
。 - 同一个类中方法调用,比如 A 方法没有声明注解,而 调用的 B 方法声明了注解,则方法 B 的事务不会生效。这也是最容易犯错的地方。解决方法也很简单,将 B 方法移到另外一个类中,或者使用
AopContext.currentProxy()
获取代理对象,然后调用 B 方法。 - 异常被手动 catch 了。