今天突然收到运营人员反馈系统出现了故障,登录系统后,发现部分接口调用超时。
下面对本次故障进行复盘。
故障描述
运营系统订单模块功能异常,报错信息有服务器异常、接口超时,其他模块功能正常。
故障排查
由于只影响了订单服务,所以我们查看订单服务的系统日志,快速定位到异常日志,报错信息如下(截取部分并脱敏处理):
SELECT * FROM jf_order_detail2021
WHERE (order_detail_no = ? AND pay_time >= ? AND pay_time <= ?)
... at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator
.java:120)
org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java
:73)
com.plhs.order.biz.service.cloudimpl.OrderCloudServiceImpl.updateOrder(OrderCloudServiceImpl.java:76)
at org.apache.dubbo.common.bytecode.Wrapper28.invokeMethod(Wrapper28.java)
Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 50, maxActive 50, creating 0, runningSqlCount 26 :
我们可以发现,直接原因是数据库连接超时,最大连接数是50个,当前连接数是50个,已经没有新的可用的连接,当前正在执行的SQL有26个。
到底什么原因导致了数据库连接超时呢?
我们继续排查日志,发现 jf_order_detail 表短时间内有大量的查询操作,具体SQL如下:
SELECT * FROM jf_order_detail2020 WHERE (order_detail_no = ? AND pay_time >= ? AND pay_time <= ?)
SELECT * FROM jf_order_detail2021 WHERE (order_detail_no = ? AND pay_time >= ? AND pay_time <= ?)
SELECT * FROM jf_order_detail2022 WHERE (order_detail_no = ? AND pay_time >= ? AND pay_time <= ?)
问题分析
先说下jf_order_detail表 的设计背景,该表是早期设计的,由于每年的订单量较大,所以设计者按照时间范围进行分片,每年度一张表。每次查询的时候带上时间分片键。
但是为什么会同时查这三张表呢?我们需要看下代码的具体逻辑,分析如下:
由于查询订单的时候只有订单号,没有时间范围,开发人员把时间范围初始化为最早时间到当前时间,也就是2020年-2022年。所以走了全库表路由,每张表都查询了一次然后汇总结果。
如果仅仅这个原因的话,性能不至于这么慢啊,我们继续看另一个字段 order_detail_no,先看下执行计划
EXPLAIN SELECT * FROM jf_order_detail2020 where order_detail_no = '1'
发现该字段并没有建立索引,type = ALL,全表扫描,扫描行数几百万条。这才是拖垮MySQL的罪魁祸首。
问题解决
给order_detail_no字段建立索引,并优化代码逻辑把时间范围缩小,能定位到具体年份。
当然,这个系统是遗留的老项目了,出现问题的代码已经很久没变动了,一般业务并不会执行此处逻辑,只有第三方个别业务会执行此处逻辑,由于并发量提升了,问题也就凸显出来了。
所以在设计之初,我们就要考虑到业务的查询场景,并尽早设计好索引,同时还需要加强对接口和数据库的性能监控。
文档信息
- 本文作者:yindongxu
- 本文链接:https://iceblow.github.io/2022/04/21/%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3%E8%B6%85%E6%97%B6/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)