连接池问题排查
问题
应用频繁出现「获取连接超时」错误,如何排查和解决?
答案
一、连接池核心原理
二、常见错误与原因
| 错误信息 | 常见原因 |
|---|---|
Connection pool exhausted | 连接被用完,未及时归还 |
Connection is not available, request timed out | 等待连接超时 |
Too many connections | 超过 MySQL max_connections |
Connection reset by peer | 空闲连接被 MySQL 关闭 |
Communications link failure | 网络中断或 MySQL 重启 |
三、排查步骤
步骤 1:确认 MySQL 侧连接数
-- 当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 各状态连接数
SHOW PROCESSLIST;
-- 各用户/Host 的连接分布
SELECT user, host, COUNT(*) AS conn_count
FROM information_schema.PROCESSLIST
GROUP BY user, host
ORDER BY conn_count DESC;
步骤 2:确认连接池配置
// Node.js mysql2 连接池示例
const pool = mysql.createPool({
host: 'localhost',
user: 'app',
database: 'mydb',
// 关键参数
connectionLimit: 10, // 最大连接数
waitForConnections: true, // 超过最大数时等待
queueLimit: 0, // 等待队列无限制
connectTimeout: 10000, // 连接建立超时 10s
idleTimeout: 60000, // 空闲超时 60s
});
HikariCP(Java)关键参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
maximumPoolSize | CPU 核心数 × 2 + 1 | 最大连接数 |
minimumIdle | = maximumPoolSize | 最小空闲连接 |
connectionTimeout | 30000ms | 获取连接超时 |
idleTimeout | 600000ms | 空闲连接超时 |
maxLifetime | 1800000ms | 连接最大存活时间 |
leakDetectionThreshold | 60000ms | 连接泄漏检测 |
连接数 ≠ 越大越好
MySQL 官方建议公式:连接数 = CPU 核心数 × 2 + 磁盘数
过多连接导致:
- 线程上下文切换开销增大
- MySQL 内存占用增加(每连接约 1~10MB)
- 锁竞争加剧
步骤 3:检查连接泄漏
连接泄漏 = 获取连接后未归还,是最常见的连接耗尽原因。
// ❌ 错误:异常时连接未释放
async function queryBad() {
const conn = await pool.getConnection();
const result = await conn.query('SELECT * FROM users'); // 若抛异常,下面不执行
conn.release(); // 永远不会执行
return result;
}
// ✅ 正确:try-finally 确保释放
async function queryGood() {
const conn = await pool.getConnection();
try {
return await conn.query('SELECT * FROM users');
} finally {
conn.release(); // 无论成功失败都释放
}
}
// ✅ 更好:使用 pool.query(自动获取和释放连接)
async function queryBest() {
return pool.query('SELECT * FROM users');
}
步骤 4:检查慢查询占用连接
-- 查看运行中的慢查询
SELECT id, user, host, db, command, time, state, info
FROM information_schema.PROCESSLIST
WHERE command != 'Sleep' AND time > 5
ORDER BY time DESC;
慢查询长时间占用连接 → 可用连接减少 → 其他请求超时。
四、连接池监控
// 定时打印连接池状态
setInterval(() => {
const status = pool.pool;
console.log({
total: status._allConnections.length, // 总连接数
free: status._freeConnections.length, // 空闲连接数
queued: status._connectionQueue.length, // 等待队列长度
});
}, 5000);
五、空闲连接被 MySQL 关闭
-- MySQL 默认 8 小时关闭空闲连接
SHOW VARIABLES LIKE 'wait_timeout'; -- 默认 28800 秒
解决方案:
| 方案 | 说明 |
|---|---|
| 连接池心跳检测 | 定期发送 SELECT 1 验活 |
maxLifetime < wait_timeout | 主动回收,避免使用已断开的连接 |
| 重连机制 | 获取连接时检测有效性 |
常见面试问题
Q1: 连接池大小应该怎么配置?
答案:
- 经验公式:
connections = CPU 核心数 × 2 + 有效磁盘数 - 实际考量:
- 微服务实例数 × 每个实例连接数 < MySQL
max_connections - 例:10 个实例,每个 20 连接 = 200,MySQL 需设 250+ 留余量
- 微服务实例数 × 每个实例连接数 < MySQL
- 压测验证:逐步增大连接数,观察 TPS 和 RT
Q2: 如何排查连接泄漏?
答案:
- HikariCP:开启
leakDetectionThreshold,超时未归还会打印获取连接的堆栈 - 监控连接池:活跃连接持续增长而不下降 = 泄漏信号
- 代码审查:搜索
getConnection确保对应release/close - MySQL 侧:
SHOW PROCESSLIST查看 Sleep 状态连接是否异常多
Q3: MySQL max_connections 被打满怎么办?
答案:
- 紧急处理:
KILL空闲连接释放资源 - 临时调大:
SET GLOBAL max_connections = 500 - 排查根因:连接泄漏?连接池配置过大?慢查询占用?
- 长期优化:引入读写分离、缓存降低数据库访问