目录
这几天公司业务突然陡然增长,日活量增大,一天有五万请求量,长久以来的服务,日志容量从40MB 突然增长到170MB,我马上意识到不妙。
没有耐心的同学可以直接跳到第5步。
1、问题暴露
首先发现日志记录异常,只提取重要信息记录如下:
大致意思是数据库连接不可用,连接超时
2025-05-17 14:45:30 ERROR http-nio--exec-30 *****.GlobalExceptionHandler 系统内部异常,异常信息
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: base - Connection is not available, request timed out after 30000ms.
### The error may exist in ***************************/RecordMapper.java (best guess)
### The error may involve *****************************.RecordMapper.insert
### The error occurred while executing an update
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: base - Connection is not available, request timed out after 30000ms.
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:97)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:439)
at jdk.proxy2/jdk.proxy2.$Proxy143.insert(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:59)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:152)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at jdk.proxy2/jdk.proxy2.$Proxy144.insert(Unknown Source)
at com.baomidou.mybatisplus.extension.service.IService.save(IService.java:60)
at cc.mrbird.febs.ah.service.impl.RecordServiceImpl.createRecord(RecordServiceImpl.java:160)
at cc.mrbird.febs.ah.service.impl.RecordServiceImpl.receiveBillingResult(RecordServiceImpl.java:248)
2、造成影响
直接造成部分数据丢失,当天晚上同事就联系我说,数据有丢失,还好日志里有全部信息,只是在入库时造成部分数据丢失。可以采取补救措施。
3、相关代码
相关代码也很简单就一两行,用的还是mybaits-plus,确实没什么可讲的
@Override
@Transactional(rollbackFor = Exception.class)
public void createRecord(Record record) {
// this.save(record);
recordMapper.insert(record);
}
4、紧急抢救
问题出现了,马上采取补救措施,先是排查问题,意识到是数据连接出现了问题,数据量太大,影响插入,此时还没意识到是连接泄露,先是增加了连接时长,以及连接数量,相关信息增加如下:
spring:
datasource:
dynamic:
hikari:
connection-timeout: 60000 # 获取连接超时时间(默认30秒)
validation-timeout: 5000 # 添加连接有效性检查超时(建议5秒)
max-lifetime: 1800000 # 连接最大存活时间(默认30分钟)
maximum-pool-size: 60 # 最大连接数(需根据压测调整)
minimum-idle: 15 # 最小空闲连接(建议与 max 一致避免扩容延迟)
idle-timeout: 60000 # 空闲连接超时释放时间(默认10分钟)
leak-detection-threshold: 30000 # 连接泄漏检测阈值(30秒)
connection-test-query: select 1 from dual # 明确测试语句(Oracle需调整)
然后重启应用,登录数据库,使用命令查看连接信息
-- MySQL
SHOW STATUS LIKE 'Threads_connected'; -- 当前活跃连接数
SHOW VARIABLES LIKE 'max_connections'; -- 最大允许连接数
-- MySQL
SHOW PROCESSLIST; -- 查看所有连接的来源和状态
这时应用已经重启了,服务竟然也恢复正常了,让我大意了,没注意到,此时连接全在updating 状态,且连接数没有任何增长。因为此时日志也不报错,就此我以为问题就解决了。结果晚上11点以后问题又复现了,第二天早上数据量少的时候,业务又恢复了。
5、原因分析
第二天又被催着去了趟公司,因为这个问题肯定是因为日活太高造成的,之前公司业务量少,都很正常,现在表数量已经到200万条,像这种日活特别高的场景也不是很多见,不是太别想搞。无奈又被催着去公司再看看。
到了公司以后,先是开启慢sql 日志,果然发现其中一条屎山代码,本可以一步插入解决的事,当时的方案是先插入在进行更新,因为是祖传的,其实我很早就注意到这个逻辑了,但是本着能跑就不动代码的原则,我也没动。而原因就出在此处,并发量少的时候,毫无问题,当数据量高的时候,更新查询时间超时,数据库连接泄露,数据丢失。
-- 查看是否开启慢查询日志(ON/OFF)
SHOW VARIABLES LIKE 'slow_query_log';
-- 查看慢查询阈值(单位:秒,默认10秒)
SHOW VARIABLES LIKE 'long_query_time';
-- 查看慢查询日志文件路径
SHOW VARIABLES LIKE 'slow_query_log_file';
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
-- 设置慢查询阈值(例如:1秒)
SET GLOBAL long_query_time = 1;
-- 记录未使用索引的查询(可选)
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 关闭慢查询日志
SET GLOBAL slow_query_log = 'OFF';
-- 检查慢查询日志状态(应为 OFF)
SHOW VARIABLES LIKE 'slow_query_log';
开启后记得关闭,否则影响性能。
慢sql 记录如下,因为慢sql 造成了连接泄露,造成普通的更新需要16秒多,这显然很有问题
UPDATE ah_**** SET imsi='999999999999999',***** WHERE (id = '2025051810169' AND phone_number = '15015472379');
# Time: 2025-05-18T09:01:32.903231Z
# User@Host: debian*** @ localhost [127.0.0.1] Id: 24**
# Query_time: 16.222893 Lock_time: 0.000004 Rows_sent: 1 Rows_examined: 39***
SET timestamp=1747558892;
6、最终解决
找到问题,就好解决了,为慢sql 加了个复合索引,其他受影响的sql 也正常了。
-- MySQL
ALTER TABLE record_table ADD INDEX idx_id_phonenumber (id, phone_number);
业务也恢复了,其他一正常了。此时我再次查看数据库的连接状态,此时数据库的连接状态都是sleep,连接数15 个 。恢复正常了。