最近在开发一个数据导出功能时,遇到了一个典型的MySQL连接问题:com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure。这个错误表面看起来像是网络连接问题,但实际排查后发现网络完全正常。错误发生时,前端页面卡住无响应,后端日志中频繁出现这个异常。
通过数据库管理工具执行show processlist命令后,发现了一个关键现象:存在大量长时间运行的查询进程,状态多为"Sleep"或"Query",持续时间普遍超过30秒。这些长连接占用了连接池资源,导致新请求无法获取数据库连接。
重要提示:遇到这类错误时,第一步永远是检查网络连通性。确认网络正常后,再深入排查其他可能性。
问题的直接诱因是在一个复杂方法上添加了@Transactional注解。这个方法执行时间较长(约2分钟),期间进行了多次数据库操作。由于事务未提交,数据库连接一直被占用,最终导致连接池耗尽。
java复制@Transactional(rollbackFor = Exception.class)
public void exportReport(ReportParams params) {
// 复杂的数据查询和处理逻辑
List<Data> dataList = queryData(params);
processData(dataList);
generateReport(dataList);
}
项目使用的是Druid连接池,默认配置如下:
当多个用户同时执行这个导出操作时,连接很快被耗尽。后续请求要么等待超时,要么直接失败。
通过EXPLAIN分析发现,部分查询没有使用索引,进行了全表扫描。一个本应毫秒级完成的查询,实际执行需要数秒,进一步加剧了连接占用问题。
将大事务拆分为多个小事务,只在必要的数据库操作上添加事务注解:
java复制public void exportReport(ReportParams params) {
// 拆分为多个小事务方法
List<Data> dataList = queryDataInTransaction(params);
processData(dataList);
saveReportInTransaction(dataList);
}
@Transactional(rollbackFor = Exception.class)
public List<Data> queryDataInTransaction(ReportParams params) {
return dataMapper.query(params);
}
@Transactional(rollbackFor = Exception.class)
public void saveReportInTransaction(List<Data> dataList) {
reportMapper.batchInsert(dataList);
}
根据实际业务需求优化Druid配置:
yaml复制spring:
datasource:
druid:
initial-size: 10
max-active: 50
min-idle: 10
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
time-between-eviction-runs-millis: 60000
针对慢查询进行优化:
sql复制-- 优化前
SELECT * FROM large_table WHERE create_time > '2023-01-01';
-- 优化后
SELECT id, name, status FROM large_table
WHERE create_time > '2023-01-01'
ORDER BY create_time DESC
LIMIT 1000 OFFSET 0;
MySQL服务器默认会为每个连接创建一个线程。当连接数超过max_connections参数设置的值时,新连接将被拒绝。即使未达到上限,长时间运行的查询也会占用连接资源。
Spring提供了7种事务传播行为,最常用的是:
正确选择传播行为对性能有重要影响。
建议在生产环境中配置连接池监控,关注以下指标:
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| Communications link failure | 网络问题 | ping数据库服务器 |
| 连接池耗尽 | show processlist | |
| 查询超时 | 检查wait_timeout设置 | |
| Lock wait timeout exceeded | 死锁 | 检查锁等待情况 |
| Too many connections | 连接泄漏 | 检查连接关闭情况 |
在实际项目中,我还遇到过几个值得注意的情况:
java复制try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
// 执行操作
}
java复制@Transactional
public void batchInsert(List<Data> dataList) {
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH);
try {
DataMapper mapper = session.getMapper(DataMapper.class);
for (Data data : dataList) {
mapper.insert(data);
if (i % 1000 == 0) {
session.flushStatements();
}
}
session.commit();
} finally {
session.close();
}
}
yaml复制spring:
datasource:
druid:
test-on-borrow: true
test-on-return: false
test-while-idle: true
这些经验教训让我深刻认识到,数据库连接管理看似简单,实则暗藏许多陷阱。特别是在高并发场景下,一个小疏忽就可能导致严重问题。