Express应用中pg-promise随机停止建立新连接且无报错
你的问题确实挺棘手的——pg-promise随机停掉数据库连接,简单查询都卡壳,重启应用还不一定能恢复,而且完全没错误日志。结合你给出的pg-monitor输出(只有连接和task启动记录,没结束或错误事件),核心大概率是连接池被未释放的连接占满阻塞了,我整理了几个排查和解决方向:
1. 先检查任务/事务的收尾逻辑
你用db.task包裹查询的代码里,藏着一个潜在风险:如果异步流程中出现未捕获的错误,或者代码路径没走完,task可能无法正确释放连接。比如你的测试路由:
app.get(apiPrefix + '/fail', async (req: express.Request, res: express.Response, next: NextFunction) => { await db.task(uuid(), async (tx) => { const time = await tx.one('select now() as time'); res.json({status: 'OK', time: time.time}); }); });
建议改成用try/catch包裹整个task流程,确保无论成功失败,连接都能被正确回收:
app.get(apiPrefix + '/fail', async (req: express.Request, res: express.Response, next: NextFunction) => { try { // 把查询结果从task里返回,再处理响应 const timeResult = await db.task(uuid(), async (tx) => { return tx.one('select now() as time'); }); res.json({status: 'OK', time: timeResult.time}); } catch (err) { next(err); // 交给错误中间件,确保task能正常结束释放连接 } });
另外要注意:如果task内部有遗漏的await,会导致异步操作脱离管控,task无法感知到流程结束,连接就会一直被占着。
2. 给连接池加上超时和监控配置
默认的pg-promise连接池没有超时机制,一旦连接被意外占用,新请求会无限等待。建议显式配置池的参数,同时加强监控:
const pgp = require('pg-promise')({ pool: { max: 10, // 别超过PostgreSQL的max_connections配置,按需调整 idleTimeoutMillis: 30000, // 闲置30秒的连接自动释放 acquireTimeoutMillis: 10000, // 获取连接超时10秒就抛出错误 }, monitor: { event: (e) => { // 记录task结束事件 if (e.finish) { console.log(`Task ${e.taskId} 完成,连接已释放`); } // 捕获错误事件 if (e.error) { console.error(`Task ${e.taskId} 出错:`, e.error); } // 实时打印连接池状态 if (e.event === 'connect' || e.event === 'release') { console.log(`连接池状态:活跃${e.pool.count}个,闲置${e.pool.idle}个`); } } } });
这样当连接获取超时的时候,错误中间件就能捕获到日志,你也能实时看到连接池是不是被占满了。
3. 去PostgreSQL端查连接状态
有时候问题不在Node这边,而是PostgreSQL里有挂起的连接。问题发生时,登录数据库执行这条查询:
SELECT pid, datname, usename, state, query_start, query FROM pg_stat_activity WHERE datname = 'database-name';
重点看有没有state为idle in transaction的连接——这类连接说明事务没正确提交/回滚,导致连接一直被占用。如果有,那你的代码里肯定有未正确收尾的事务逻辑。
4. 捕获全局未处理的Promise异常
Node里未处理的Promise rejection会让进程状态异常,虽然不会直接崩掉,但可能干扰pg-promise的连接池逻辑。在应用顶部加个全局监听:
process.on('unhandledRejection', (reason, promise) => { console.error('未捕获的Promise异常:', promise, '原因:', reason); // 必要时可以在这里触发应用重启,避免连接池一直阻塞 });
这样能帮你找到那些藏在代码里、没被捕获的异步错误,它们很可能就是连接没释放的元凶。
5. 检查pg-promise和pg的版本兼容性
版本不兼容也会导致奇怪的连接池问题,你可以执行npm list pg pg-promise查看当前版本,然后去pg-promise的官方仓库确认它和依赖的pg包的版本匹配要求,尽量升级到最新稳定版。
最后提醒下:问题发生时先看连接池的活跃连接数是不是达到了max值,如果是,那肯定是有连接没被释放,结合pg_stat_activity的结果和未处理rejection日志,基本就能定位到具体的代码路径了。
内容的提问来源于stack exchange,提问作者Johan Nordberg

