返回

Node.js 连接 MySQL 报 ECONNRESET 错误?原因及解决

mysql

Node.js 后端连接 MySQL 数据库出现 ECONNRESET 错误

最近做项目,Node.js 后端连 MySQL 数据库,一开始挺好,查询都没问题。可服务器闲置几小时后,第一次查询就报错,报read ECONNRESET错误,很烦。

错误堆栈像这样:

Error
at exports.Error.utils.createClass.init (D:\home\site\wwwroot\errors.js:180:16)
at new newclass (D:\home\site\wwwroot\utils.js:68:14)
at Query._callback (D:\home\site\wwwroot\db.js:281:21)
at Query.Sequence.end (D:\home\site\wwwroot\node_modules\mysql\lib\protocol\sequences\Sequence.js:78:24)
at Protocol.handleNetworkError (D:\home\site\wwwroot\node_modules\mysql\lib\protocol\Protocol.js:271:14)
at PoolConnection.Connection._handleNetworkError (D:\home\site\wwwroot\node_modules\mysql\lib\Connection.js:269:18)
at Socket.EventEmitter.emit (events.js:95:17)
at net.js:441:14
at process._tickCallback (node.js:415:13)

我在云服务器和本地环境都遇到了这个错误。 琢磨半天,来给大家说说我是怎么解决的。

一、问题原因分析

这问题,我感觉很可能是 Node.js 和 MySQL 数据库之间的连接断了,可能是因为连接有时间限制。

进一步分析:

  1. 连接超时: MySQL 服务器或者网络设备(比如防火墙、负载均衡器)为了节省资源,可能会主动关闭长时间空闲的连接。
  2. 网络波动: 不稳定的网络环境也可能导致连接中断。
  3. 连接池配置问题: 虽然使用了连接池,node-mysql 模块在处理断开连接时, 可能没处理好,导致旧连接没有及时从池中移除。
  4. 数据库服务器配置问题:数据库服务器设置了较短的连接超时时间.

二、解决方案

针对上述原因,我整理了下面几个解决方案。

1. 使用连接池并正确配置

node-mysql 提供了连接池功能,连接池能自动管理多个数据库连接,包括创建、复用和释放连接。正确配置连接池,可以很大程度上避免连接超时问题。

原理: 连接池会维护一定数量的数据库连接,当应用需要访问数据库时,从池中获取一个连接,用完后再归还到池中。这样可以避免频繁创建和销毁连接带来的开销。

代码示例:

const mysql = require('mysql');

const pool = mysql.createPool({
  connectionLimit: 10, // 连接池最大连接数(根据实际需求调整)
  host: 'your_mysql_host',
  user: 'your_mysql_user',
  password: 'your_mysql_password',
  database: 'your_mysql_database',
  waitForConnections: true, // 当连接池中没有可用连接时,是否等待连接(true:等待;false:抛出错误)
  queueLimit: 0, // 等待队列的长度限制(0:不限制)
    acquireTimeout: 10000 // 连接池获取连接超时
});

// 从连接池获取连接
pool.getConnection((err, connection) => {
  if (err) {
    console.error('获取连接失败:', err);
    return;
  }

  // 执行查询
  connection.query('SELECT * FROM your_table', (err, results) => {
    // 释放连接
    connection.release();

    if (err) {
      console.error('查询失败:', err);
      return;
    }

    console.log('查询结果:', results);
  });
});

安全建议:

  • 不要把数据库的用户名、密码等敏感信息直接写在代码里, 最好从环境变量或者配置文件读取。
  • 合理设置连接池大小, 太小会影响并发, 太大会增加数据库负担。

进阶技巧:
可以设置idleTimeoutMillis, 超时会被销毁, 这样旧连接会被清理掉.

const pool = mysql.createPool({
    //...其他配置...
    idleTimeoutMillis: 30000 // 空闲连接超时时间(毫秒),根据需要调整。
});

2. 心跳检测 (Connection Keep-Alive)

心跳检测就是定期向数据库发送一个简单的查询(比如 SELECT 1),以保持连接的活跃状态。

原理: 通过定期发送心跳查询,可以防止连接因为空闲时间过长而被关闭。

代码示例:

const mysql = require('mysql');

const pool = mysql.createPool({ /* 连接池配置 */ });

function keepAlive() {
  pool.getConnection((err, connection) => {
    if (err) {
      console.error('心跳检测:获取连接失败', err);
      return;
    }

    connection.query('SELECT 1', (err) => {
      connection.release();
      if (err) {
        console.error('心跳检测:查询失败', err);
        return;
      }
      //什么也不做
    });
  });
}

// 每隔一段时间执行心跳检测(例如,每 5 分钟)
setInterval(keepAlive, 300000);

额外说明:
心跳间隔需要根据实际情况来设置.如果数据库的wait_timeout设置的较小, 心跳间隔就应该更频繁一些。

3. 错误重连

即使做了上面的处理, 连接还是有可能断开, 毕竟网络这东西, 谁也说不准。所以,咱们还得加个错误重连机制。

原理: 在捕获到 ECONNRESET 错误后, 尝试重新建立数据库连接.

代码示例:
我们改造一下数据库查询部分代码:


function executeQuery(sql, values, retries = 3) {
    return new Promise((resolve, reject) => {
        pool.getConnection((err, connection) => {
            if (err) {
                console.error('获取连接失败', err);
                 if (retries > 0 )
                 {
                     console.log(`尝试重新连接...剩余重试次数:${retries}`);
                     setTimeout(()=>{
                            executeQuery(sql,values,retries - 1).then(resolve).catch(reject)

                        }, 2000); //间隔两秒进行重试
                     return;
                 }
                reject(err);
                return;
            }

            connection.query(sql, values, (err, results) => {
                connection.release();
                if (err) {
                   if (err.code === 'ECONNRESET' && retries > 0) {
                        console.error('连接中断,尝试重新连接...');
                         setTimeout(()=>{
                            executeQuery(sql,values,retries - 1).then(resolve).catch(reject)

                        }, 2000); //间隔两秒进行重试

                    }
                     else
                    {
                      reject(err);
                    }
                  return;
                }
                resolve(results);
            });
        });
    });
}

// 使用方法:
executeQuery('SELECT * FROM your_table', [])
  .then(results => {
    console.log('查询结果:', results);
  })
  .catch(err => {
    console.error('查询出错:', err);
  });

额外说明:

  • 这里设置了最大重试次数(retries = 3), 防止无限重连。
  • 重连间隔可以适当调整, 不要太频繁。

4. 检查并调整 MySQL 服务器配置

有时候问题不在我们这边, 而在数据库服务器那边。我们可以检查一下 MySQL 的配置,看看有没有需要调整的地方。

操作步骤:

  1. 登录 MySQL 服务器:

    mysql -u your_mysql_user -p
    
  2. 查看 wait_timeoutinteractive_timeout 的值:

    SHOW VARIABLES LIKE 'wait_timeout';
    SHOW VARIABLES LIKE 'interactive_timeout';
    

wait_timeout 是服务器关闭非交互式连接之前等待的时间(秒),interactive_timeout是服务器关闭交互式连接之前等待活动的时间。

  1. 如果需要, 修改这些值:
    可以通过修改 my.cnf (或者 my.ini) 文件来修改这些配置, 修改后,需要重启MySQL 服务器才能生效。 举例, 修改my.cnf:
[mysqld]
wait_timeout = 28800   # 8 hours
interactive_timeout = 28800 #8 hours

或者可以在运行时设置(不推荐,重启失效):

SET GLOBAL wait_timeout = 28800;
SET GLOBAL interactive_timeout = 28800;

注意:

  • 调大这些值可以减少连接断开的频率,但也会占用更多的服务器资源。所以,要根据实际情况权衡。

5. 检查网络设备配置

除了数据库服务器配置, 我们还需要考虑网络设备。例如:防火墙, 负载均衡器等。

原理:

有些网络设备会有连接超时设置,会自动关闭长时间空闲的连接。我们需要找出这些设备,并调整它们的配置,或者通过心跳包机制来避免连接被关闭。

检查内容:

  • 防火墙: 查看防火墙规则,确保没有阻止 Node.js 应用程序与 MySQL 数据库之间的连接。
  • 负载均衡器: 检查负载均衡器的连接超时设置。
  • ** 其他网络设备:** 路由器,交换机等,也可能有关闭空闲连接的策略。

6. 升级node-mysql 模块

旧版本的 node-mysql 可能存在一些 bug 或者对连接处理不完善。 尝试更新一下,可能直接解决问题。

npm install mysql@latest

通过上面这些方法, 大部分ECONNRESET问题应该都可以搞定了. 大家可以根据自己的情况来选择合适的方案。关键还是要把原理弄懂,才能更好地应对各种问题。