一、问题描述
业务下单链路(大事务)整体耗时较高,影响整体接口性能
1、什么是大事务
运行时间长,长时间未提交的事务称之为大事务
2、大事务会带来那些问题
a、死锁
b、数据库连接池被占满
c、业务响应时间长
d、……
3、问题详情
使用trace分析链路耗时情况,整体耗时链路如下图
从图中可看到,业务耗时只有245ms,而整体的耗时时间却是1303ms,两者之间相差了1000ms左右,这个时候很奇怪,时间去哪里了?
二、分析工具
arthas
三、问题分析思路与过程
a、trace调用分析
trace com.xx.horder.service.impl.xxProductServiceImpl byconuponOrder
b、从链路耗时结果来看,代理类方法耗时1303ms
[1303.276852ms] com.xx.horder.service.impl.xxProductServiceImpl$$EnhancerBySpringCGLIB$$8c9b855:byconuponOrder
c、业务整体整体耗时才只有245ms,存在一个较高的Http接口调用耗时209ms
[245.071658ms] com.xx.horder.service.impl.xxProductServiceImpl:byconuponOrder
# Http耗时接口
[209.158268ms] com.xx.horder.util.AirHttpClientUtil:postJson() #2005
d、trace调用链中,有个代理类耗时比较高,猜想应该是Spring事务注解(Spring事务是由AOP实现的)
e、使用jad反编译下代码,从图中可看到使用了Spring注解
jad com.xx.horder.service.impl.xxProductServiceImpl:byconuponOrder
jad反编译结果,查看具体代码逻辑(太长了,简化部分无用代码)
@Override
@Transactional
public CouponProductBuyResponseDto buyCouponOrder(List<CouponOrderEx> couponOrders, Order order, CouponProductBuyRequestDto requestDto) {
String orderProductUrl = this.config.getProductSystemUrl() + "/xxController/xxRecord";
/*2005*/ orderResponse = (CreateOrderResponse)AirHttpClientUtil.postJson((String)orderProductUrl, (Object)createOrderRequest, CreateOrderResponse.class);
/*2026*/ this.orderMapper.insert(order);
/*2029*/ if (AirStringUtil.isNotBlank((CharSequence)order.getChannelCustomerNo()) && Integer.parseInt(order.getChannelCustomerNo()) > 0) {
/*2033*/ this.orderIndexMapper.insert(orderIndex);
}
/*2041*/ for (int i = 0; i < couponOrders.size(); ++i) {
/*2047*/ this.couponOrderMapper.insert((CouponOrder)l);
/*2060*/ this.couponOrderParamMapper.insert(couponOrderParam);
/*2067*/ this.orderSubOrderMapper.insert(orderSubOrder);
}
/*2069*/ resultDTO.success();
/*2070*/ resultDTO.setCouponOrders(couponOrderExtList);
/*2071*/ return resultDTO;
}
catch (Exception e) {
}
}
f、从代码分析逻辑来看大概分几步
i、通过Http接口调用外部系统
ii、执行了5次Insert操作,其中有三次insert在循环里面
g、到这里能分析到两个可能得原因
i、长事务问题,应该是并发较高,数据库操作频繁,导致整体事务提交比较慢
ii、for循环插入问题,批量操作耗时长
针对长事务问题分析,去查看当前Mysql数据库的执行的事务情况及耗时
查看下mysql事务耗时情况
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>50
针对for循环插入问题分析
这个问题应该不存在,如果是for循环操作比较多,Trace的耗时结果会打印出来,但这里只操作了一次,也是可以优化点(针对业务的情况分析)
h、从以上结果分析,证明了我们的猜想
此次问题由于长事务问题导致
具体的解决方案
a、少用声明式事务,通过@Transactional
注解
原因:
Transactional注解一般是加载业务Service方法上,会导致整个方法都处于事务中,常常代码中包含远程调用,操作的数据比较多,逻辑比较复杂,导致整体事务粒度比较大
解决方案:
1、使用编程式事务,灵活控制粒度
2、缩小Transactional
注解的范围
b、将查询操作,放到事务外,一般查询操作不需要开启事务
c、远程调用之类的,放到事务外
d、事务中避免处理太多数据,数据操作不易太复杂
e、……
本文来自投稿,不代表TakinTalks稳定性技术交流平台立场,如若转载,请联系原作者。