简介
Spring支持两种使用事务的方式:声明式和编程式。声明式事务是大多数程序员使用的,一个注解@Transactional走天下,由于事务的特性及事务是由aop技术来实现的,往往会碰到一些坑,使得事务失效或性能受损,甚至发生死锁现象。
事务失效的坑:AOP技术限制引起的
Spring中的事务是AOP实现的,Srping AOP使用JDK动态代理或CGLIB来创建代理对象。
默认情况下,如果需要代理的对象实现了接口,则使用JDK动态代理,否则使用CGLIB。
可以参考文档:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying
或源码:
org.springframework.aop.framework.DefaultAopProxyFactory
由于JDK动态代理或CGLIB来创建代理技术限制,某些方法或行为不能创建代理行为或自动使用代理对象调用方法,会使得事务失效。
1、final方法添加@Transactional,事务不生效;
2、static方法添加@Transactional,事务不生效;
3、非public方法添加@Transactional,事务不生效;
可以通过class-based proxies or consider using AspectJ compile-time or load-time weaving解决。
4、同一个类的带有事务注解@Transactional的两个方法self-invocation行为,事务不生效;
jdk动态代理技术肯定失效,可以通过CGLIB技术规避。
事务的坑:Spring实现机制引起的
1、抛出受检异常Exception无法回滚
默认情况下,只有非受检异常RuntimeException、Error发生时,事务才会回滚。受检异常Exception发生时不会回滚。
图片来源:https://javadevcentral.com/checked-and-unchecked-exception-in-java
事务回滚源码:
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
默认情况下,非受检异常会回滚:
我们可以设置回滚Exception异常类型,来解决受检异常不回滚的问题:
@Transactional(rollbackFor=Exception.class)
处理逻辑如下:
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
2、子事务出异常回滚当前事务,导致父方法也无法提交事务
事务的默认传播行为为Propagation.REQUIRED,子事务可以设置Propagation.REQUIRES_NEW,在独立事务中执行。
3、方法内try catch异常,不再抛给事务框架,不会回滚事务
自己吞掉了异常,Spring框架不会探测到异常。
4、事务多个业务有异步执行,异常不抛出,事务不会回滚
事务的实现涉及到java的ThreadLocal特性,如果异步执行,事务信息丢失或异常丢失,导致事务执行或回滚。
来源:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-decl-explained
5、一个事务中多个业务有同步或异步执行,使用不同的数据源,事务不会生效
使用spring的本地事务,同一个事务内必须一个数据源,不能跨数据源,否则必须使用分布式事务。
来源:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-local
6、事务所在的类不是spring容器管理的
7、未配置事务管理器
8、其他aop 顺序问题,并且吞并异常,事务失效
事务aop有自己默认的顺序:
org.springframework.transaction.annotation.EnableTransactionManagement#order
如果其他开发者或者框架引入的aop顺序和事务的顺序相同,由于Spring框架aop排序问题,很可能导致一些问题的发生。
事务的坑:数据库引起的
1、数据库引擎不支持事务
事务的坑:大事务引发问题
1、锁定数据太多,容易造成大量阻塞或死锁问题和锁等待时间长而引发的锁超时问题;
2、回滚记录占用大量存储空间,事务回滚时间长;
3、并发情况下数据库连接处被占满;
4、事务执行时间长,事务结束后才写入binlog,容易造成数据库主从延迟
如何避免大事务:
1、不要一股脑的用@Transactional注解;
2、大事务拆分为独立的小事务;
3、事务避免PRC调用-分布式事务;
4、事务中避免一次处理太多的数据;
5、能不用事务就不用;
小结