前言
大部分生产事故,都不是什么高深的技术问题,而是最基础的配置没做对。
日志没配滚动,磁盘满了;连接池没设上限,数据库连接被耗尽;事务没设超时,一个慢查询把整张表锁死。这三个问题,我这些年反复见到过,而且每次还都是同样的剧本:测试环境没问题,上线跑几天,突然崩了,查半天发现是配置问题。
这里分享一些排查方法和配置方案,如果你正在维护 Java 后端服务,建议对照检查一遍。
坑一:日志爆盘
问题表现
服务突然写不进日志,或者直接报磁盘空间不足。登上去一看,app.log 已经几十个 G,把磁盘占满了。
Spring Boot 默认的 Logback 配置不会自动清理日志。日志文件会一直变大,直到磁盘撑爆。
排查方法
# 查看磁盘占用
df -h
# 查看日志目录大小
du -sh /path/to/logs/*
# 找出最大的日志文件
find /path/to/logs -type f -name "*.log" -exec ls -lh {} \; | sort -k5 -hr | head -10解决方案
配置 Logback 滚动策略,让日志自动按大小切分、按时间清理:
logging:
file:
name: app.log
logback:
rollingpolicy:
max-file-size: 100MB # 单个文件最大 100MB,满了自动切
max-history: 30 # 只保留 30 天,超期自动删
total-size-cap: 3GB # 所有日志加起来不超过 3GB参数解释:
max-file-size:单个日志文件上限,满了就生成新文件(app.log → app.1.log → app.2.log)max-history:保留天数,30 天前的自动删除total-size-cap:总大小上限,防止日志无限增长
如果日志量大,考虑接入 ELK 或 Loki,把日志集中收集,本地只保留最近几天。
坑二:连接泄露
问题表现
服务运行一段时间后,数据库操作开始超时,报“连接池已满”或“等待连接超时”,重启服务能暂时恢复,但过几天又出现。
HikariCP 默认最大连接数是 10。如果代码里有连接没归还,或者慢查询占着连接不放,很快就耗尽了。
排查方法
第一步:开启泄露检测
spring:
datasource:
hikari:
leak-detection-threshold: 60000 # 连接超过 60 秒没归还就报警开启后,日志里会出现类似这样的警告:
Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@xxx, stack trace follows顺着堆栈就能找到哪段代码没关闭连接。
第二步:查看连接池状态
如果集成了 Actuator,可以访问 /actuator/metrics/hikaricp.connections.active 查看活跃连接数。
第三步:检查慢查询
-- MySQL 查看当前运行的慢查询
SHOW FULL PROCESSLIST;
-- 查看锁等待
SELECT * FROM information_schema.innodb_trx WHERE trx_state = 'LOCK WAIT';解决方案
配置层面:
spring:
datasource:
hikari:
maximum-pool-size: 50 # 最大连接数,根据数据库性能调整
minimum-idle: 10 # 最小空闲连接
connection-timeout: 30000 # 获取连接超时时间
idle-timeout: 600000 # 空闲连接超时释放
max-lifetime: 1800000 # 连接最大存活时间(30分钟)
leak-detection-threshold: 60000 # 泄露检测代码层面:
用 try-with-resources 确保连接关闭
检查是否有手动获取 Connection 没关闭的地方
检查 MyBatis/JPA 的批量操作是否配置了正确的 flush 策略
坑三:事务锁表
问题表现
某个接口突然变慢,然后其他接口也跟着卡,查数据库发现大量锁等待,一条慢 SQL 把整张表锁住了。
Spring Boot 的 @Transactional 默认不设置超时时间。如果一个事务里处理大量数据(比如批量导入 10 万条),会长时间持有数据库锁,其他操作全部阻塞。
排查方法
-- 查看当前锁等待
SELECT * FROM information_schema.innodb_trx
WHERE trx_state = 'LOCK WAIT';
-- 查看正在执行的长事务
SELECT * FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 30;
-- 查看锁等待详情
SELECT * FROM performance_schema.data_lock_waits;解决方案
第一步:给事务设超时
@Transactional(timeout = 30, rollbackFor = Exception.class)
public void batchProcess(List<Data> dataList) {
int batchSize = 100;
for (int i = 0; i < dataList.size(); i += batchSize) {
List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
processBatch(batch);
}
}参数解释:
timeout = 30:事务超过 30 秒自动回滚,释放锁rollbackFor = Exception.class:遇到任何异常都回滚分批处理:10 万条分成每 100 条一批,缩短单次事务时长
第二步:优化慢查询
检查是否有全表扫描
确认索引是否正确使用
避免在事务里做 RPC 调用、文件 IO 等耗时操作
第三步:考虑读写分离
如果读多写少,把读请求分流到从库,减少主库锁竞争。
三个坑的共同点
共同点: Spring Boot 的默认配置是为了“开发方便”,不是为“生产稳定”,上线前必须按生产环境重新调一遍。
生产环境检查清单
上线前对照检查:
日志是否配置了滚动和清理?
连接池最大连接数是否合理?
是否开启了连接泄露检测?
事务是否设置了超时时间?
批量操作是否分批处理?
是否有慢查询监控?
磁盘空间是否有告警?
数据库服务器资源是否充足?
数据库服务器怎么选
这三个坑,配置能解决大部分问题,但底层资源也很重要。如果数据库跑在资源不足的机器上,再怎么调优也扛不住。
云数据库 vs 自建 vs 裸金属:
如果数据库是核心瓶颈,裸金属的独享 IO 和物理隔离能减少“邻居影响”,性能更可预测。
