Node.js 使用 mysql2 长连接踩坑实录:ETIMEDOUT、连接复用误区与稳定运行方案

2026-09-05 340 浏览 0 评论 0 字

在一个长期运行的 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);
}

运行一段时间后,开始出现:

  • ETIMEDOUT
  • UnhandledPromiseRejectionWarning
  • 数据库请求偶发失败

二、核心误区: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);

九、问题整体归因

该类问题通常由以下因素叠加导致:

  1. 手动缓存 connection 对象
  2. 使用已失效的连接
  3. 长时间运行导致 idle timeout
  4. 误用 keepAlive 配置
  5. 缺乏异常处理机制

十、总结

在 Node.js 中使用 mysql2 时,连接管理是长期运行系统稳定性的关键。ETIMEDOUT 并非偶发异常,而是连接生命周期管理不当的直接结果。理解 MySQL 断开机制、区分 TCP keepAlive 与数据库连接策略,以及正确使用连接池,是避免此类问题的核心。


发布评论

发布评论前请先 登录。
0 评论
点赞
收藏

评论列表 0

暂无评论