Node.js 使用 mysql2 长连接踩坑实录:ETIMEDOUT、连接复用误区与稳定运行方案
在一个长期运行的 Node.js 脚本中(如爬虫或数据处理任务),使用 mysql2/promise 访问 MySQL 时,经常会遇到如下问题:
Error: connect ETIMEDOUT
UnhandledPromiseRejectionWarning以及:
Ignoring invalid configuration option passed to Connection:
enableKeepAlive
keepAliveInitialDelay这些问题往往不是单点错误,而是多个认知偏差叠加后的结果。本文结合实际运行场景,对问题进行完整梳理。
一、典型代码结构与现象
常见结构如下:
const mysql = require('mysql2/promise');
let conn;
async function getConnect() {
if (!conn) {
conn = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'xxx',
database: 'xxx'
});
}
return conn;
}
async function main(word = '') {
if (!word) process.exit();
const con = await getConnect();
// 一些数据库操作
await main(nextWord);
}运行一段时间后,开始出现:
ETIMEDOUTUnhandledPromiseRejectionWarning- 数据库请求偶发失败
二、核心误区:createConnection 并不会复用连接
mysql2/promise.createConnection() 的行为是:
每调用一次,就创建一个新的连接
如果代码中通过变量缓存连接对象,看起来像 复用 ,但实际情况是:
- 连接对象仍然存在
- 底层 TCP 连接可能已经断开
- 应用层无法感知连接已经失效
这就形成了 假连接 。
三、连接为何会失效
MySQL 服务端默认会主动断开空闲连接(idle connection),常见原因包括:
1. wait_timeout 限制
MySQL 参数:
SHOW VARIABLES LIKE 'wait_timeout';当连接长时间没有请求时,会被服务器关闭。
2. 长时间运行的递归任务
递归结构:
await main()特点:
- 持续运行
- 请求间隔不稳定
- 连接可能长时间空闲
3. TCP 层连接被回收
在某些网络环境中(如 NAT),长连接可能被清理。
四、关于 keepAlive 的误用
在尝试解决问题时,常见写法:
mysql.createConnection({
enableKeepAlive: true,
keepAliveInitialDelay: 0
});会出现警告:
Ignoring invalid configuration option passed to Connection原因是:
enableKeepAlive、keepAliveInitialDelay并不是mysql2支持的配置项- 它们属于 Node.js 底层
net.Socket或 HTTP Agent 的参数 - 在 mysql2 中不会生效
五、连接失效的真实本质
连接问题本质是:
应用层持有一个 已断开 的连接对象,并继续使用
表现为:
- 对象存在
- 方法可调用
- 实际请求失败(超时或拒绝)
六、稳定运行的连接管理方式
1. 使用连接池
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'xxx',
database: 'xxx',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});调用方式:
const [rows] = await pool.query('SELECT ...');连接池特性:
- 自动分配连接
- 自动回收连接
- 避免复用失效连接
- 连接生命周期由内部管理
2. 定期维持连接活跃
setInterval(async () => {
await pool.query('SELECT 1');
}, 60000);通过轻量查询维持连接活跃,避免被 MySQL 关闭。
3. 单连接场景下的处理方式
let conn;
async function getConnect() {
if (conn) {
try {
await conn.ping();
return conn;
} catch (e) {
conn = null;
}
}
conn = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'xxx',
database: 'xxx'
});
return conn;
}通过 ping() 判断连接有效性。
七、递归结构带来的隐患
递归调用:
await main()在长时间运行中存在问题:
- 控制流不清晰
- 错误难以集中处理
- 不利于限流与重试
- 逻辑耦合度高
八、Promise 未捕获异常问题
报错:
UnhandledPromiseRejectionWarning说明存在未处理异常。
典型处理:
try {
await main();
} catch (err) {
console.error(err);
}或:
process.on('unhandledRejection', console.error);九、问题整体归因
该类问题通常由以下因素叠加导致:
- 手动缓存 connection 对象
- 使用已失效的连接
- 长时间运行导致 idle timeout
- 误用 keepAlive 配置
- 缺乏异常处理机制
十、总结
在 Node.js 中使用 mysql2 时,连接管理是长期运行系统稳定性的关键。ETIMEDOUT 并非偶发异常,而是连接生命周期管理不当的直接结果。理解 MySQL 断开机制、区分 TCP keepAlive 与数据库连接策略,以及正确使用连接池,是避免此类问题的核心。






