You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Heroku环境下Node.js+MySQL后端频繁崩溃问题求助

问题排查与修复方案

核心问题拆解

  1. 连接池重复实例化:每次调用getPool()都新建一个连接池,高频请求下会生成大量独立的连接池,直接耗尽数据库连接数和服务器资源,导致服务崩溃。
  2. 数据库连接泄漏:DAO中仅在查询结果非空时释放连接,出错、结果为空的场景下连接未释放,连接池很快被占满,后续请求无法获取连接,服务直接挂死。
  3. 异步错误无法捕获:路由里的try/catch对回调式异步函数无效,出错时无法返回正确的500响应,导致请求堆积。
  4. 从日志截图来看,服务崩溃大概率是触发了数据库连接超限或服务器资源耗尽的错误。

分步修复

1. 将连接池改为单例模式

全局仅初始化一次连接池,避免重复创建:

const mysql = require('mysql');

// 全局唯一连接池实例
const pool = mysql.createPool({   
  host:'...',         
  user: '...',
  password: 'xxxxxx',
  database: 'hxxxxxx',
  connectionLimit: 10, // ClearDB免费版连接数有限,别设50,建议10以内
  waitForConnections: true,
  connectTimeout: 60000
});

module.exports.getPool = () => pool;

注意:ClearDB免费套餐的最大连接数通常为10-20,connectionLimit设太高会直接触发数据库连接超限错误,需根据实际配额调整。

2. 彻底修复连接泄漏问题

确保所有分支(成功、失败、结果为空)都释放数据库连接:

const connDB = require('../db/DBConnection');

const retrieveBestScoredServices=(idUser,zipcode,callback)=>{
    connDB.getPool().getConnection(function(err, con){
        if (err) {
            console.log("[mysql连接错误]", err);
            callback([]); // 返回空结果避免前端请求挂起
            return;
        }

        con.query(`
            SELECT U.ID_USER idUserOwner
            -- 补全你的完整SQL语句(原SQL不完整)
            WHERE S.APPROVED_STATUS=1
            ORDER BY S.RATING_TOTAL DESC LIMIT 20;
        `, [/* 传入正确的变量参数 */], function(err, services) {
            // 无论查询结果如何,优先释放连接
            con.release();
            
            if (err) {
                console.log("[mysql查询错误]", err);
                callback([]);
                return;
            }
            callback(services || []); 
        });    
    });
}

关键:con.release()必须放在查询回调的最外层,确保100%执行,避免任何场景下的连接泄漏。

3. 修复异步错误捕获逻辑

将DAO改为Promise风格,路由使用async/await,让错误能被正确捕获:

DAO改造为Promise风格:

const connDB = require('../db/DBConnection');

const retrieveBestScoredServices=(idUser,zipcode)=>{
    return new Promise((resolve, reject) => {
        connDB.getPool().getConnection(function(err, con){
            if (err) {
                console.log("[mysql连接错误]", err);
                reject(err);
                return;
            }

            con.query(`
                SELECT U.ID_USER idUserOwner
                WHERE S.APPROVED_STATUS=1
                ORDER BY S.RATING_TOTAL DESC LIMIT 20;
            `, [idUser, zipcode], function(err, services) {
                con.release();
                if (err) {
                    console.log("[mysql查询错误]", err);
                    reject(err);
                    return;
                }
                resolve(services || []);
            });    
        });
    });
}

路由使用async/await捕获错误:

routes.post('/getTheBestServices', async(req, resp)=>{
    const { zipcode, idUser } = req.body;
    try {
        const services = await srv.retrieveBestScoredServices(idUser, zipcode);
        resp.send(services);
    } catch (error) {
        console.error("获取高评分服务失败:", error);
        resp.status(500).send({"error":"获取高评分服务失败"});
    }
});

这样异步错误能被try/catch正确捕获,避免请求无响应堆积。

4. 额外优化建议

  • 添加请求限流:使用express-rate-limit中间件限制高频请求,防止瞬间请求打爆服务:
    const rateLimit = require('express-rate-limit');
    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15分钟周期
      max: 100 // 每个IP最多100次请求
    });
    app.use(limiter);
    
  • 优化SQL语句:补全完整的SQL逻辑,给S.APPROVED_STATUS、S.RATING_TOTAL等查询字段添加索引,提升查询速度,减少连接占用时间。
  • 监控连接状态:添加定时日志监控活跃连接数,方便排查问题:
    setInterval(() => {
      const activeConnections = connDB.getPool()._allConnections.length;
      console.log(`当前活跃数据库连接数: ${activeConnections}`);
    }, 5000);
    

验证步骤

  1. 将修复后的代码部署到Heroku
  2. 模拟高频请求(如使用Postman批量请求、快速刷新移动端页面)
  3. 查看Heroku日志,确认无连接泄漏、连接池重复创建的错误
  4. 验证服务在高频请求下不会崩溃

内容的提问来源于stack exchange,提问作者nicoaguilar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:45:00