日志爆盘、连接泄露、事务锁表处理经验分享

日志爆盘、连接泄露、事务锁表处理经验分享

前言 大部分生产事故,都不是什么高深的技术问题,而是最基础的配置没做对。 日志没配滚动,磁盘满了;连接池没设上限,数据库连接被耗尽;事务没设超时,一个慢查询把整张表锁死。这三个问题,我这些年反复见到过,而且每次还都是同样的剧本:测试环境没问题,上线跑几天,突然崩了,查半天发现是配置问题。 这里分享一

前言

大部分生产事故,都不是什么高深的技术问题,而是最基础的配置没做对。

日志没配滚动,磁盘满了;连接池没设上限,数据库连接被耗尽;事务没设超时,一个慢查询把整张表锁死。这三个问题,我这些年反复见到过,而且每次还都是同样的剧本:测试环境没问题,上线跑几天,突然崩了,查半天发现是配置问题。

这里分享一些排查方法和配置方案,如果你正在维护 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 和物理隔离能减少“邻居影响”,性能更可预测。

评论