-LCN模式基本特征
- 该模式对代码的嵌入性较低。
- 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
- 该模式下的事务提交和回滚是由本地事务方控制,对于数据一致性上有较高的保障。LCN并不生产事务,LCN只是本地事务的协调工。LCN模式是通过代理Connection的方式实现对本地事务的操作,然后再由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理(分配、释放)。
- LCN模式缺陷在于代理的连接需要随事务发起方一起释放连接,增加了连接占用的时间。LCN模式实质上是代理数据库连接,占用资源时间长,直至TM通知完成提交结束才释放。
- LCN连接重用机制,当模块在同一次事务下被重复执行时,连接资源会被重用,提高连接的使用率。
问题1、锁等待超时异常
场景
短时间内对相同数据频繁业务操作,当该业务操作比较重的情况下,偶尔造成“锁等待超时”异常。
分析
由于LCN模式的代理连接需要跟随事务发起方一起释放连接,当该模块业务代码较重情况下,造成连接占用时间较长,对于相同数据Mysql存在行级锁,高并发场景造成“锁等待超时”异常。
解决方案
方案一:优化业务代码,降低业务运行复杂度。
方案二:业务代码添加业务频繁操作限制。(注解+AOP)
方案三:设置数据库该业务表的锁等待超时属性,延时超时时间。
show variable like "lock_wait";
问题2、TM通知不到TC,TC不进行事务提交
场景
TC采取微服务高可用部署,示例:TC业务微服务系统采用两台实例机器进行部署,TM通知不到TC,TC不进行事务提交。
分析
TC创建或者加入事务组时,TM会统一将TC信息维护到Redis中,其中包含TC的modId属性,TC微服务默认将modId设置为服务名。当TC采取高可用多实例机器方式部署时,相同TC微服务不同实例其modId相同,当TM根据modId通知事务提交时,无法准确定位具体TC实例机器,存在TM通知实例1提交事务,但实际上通知到实例2去提交事务,实例2不存在本地事务,实例1又无法提交事务。
解决方案
TC微服务采取IP+Port方式设置modId属性,精确定位到具体实例机器。
问题3、注解@LcnTransaction方法中,异步线程任务无法提交数据库事务,数据无法入库,报错:“connection holder is null”
场景
@LcnTransaction方法中存在异步线程任务,异步线程任务无法提交数据库事务,报错:“connection holder is null”,数据无法入库。
分析
方法添加@LcnTransaction注解,LCN会拦截当前方法内所有的数据库Connnection,生成LCN代理连接。同时,异步线程执行的异步代码中涉及的本地数据库事务默认会继承主线程事务上下文,其中包含继承共享使用主线程的数据库连接(LCN代理连接)。因此,当主线程事务完成提交,并关闭了数据库代理连接,那么后续异步子线程再进行事务提交时将报数据库连接错误。
类DTXLocalContext,private final static ThreadLocal<DTXLocalContext> currentlocal = new InheritableThreadLocal<>());public static DTXLocalContext cur(){ return currentLocal.get(); }
解决方案
子线程执行的异步代码设置本地事务不使用LCN代理连接,采取本地数据库连接来操作当前代码的数据库的事务提交。
DTXLocalContext dtx = DTXLocalContext.curl();if(Objects.nonNull(dtx)){ dtx.setProxy(false); }