使用Promise.all()调用Node-Postgres查询触发连接超时错误
Postgres连接超时问题分析与解决(Promise.all并行调用场景)
错误原因
1. 连接池资源竞争导致超时
你的connectDB函数每次调用都会从连接池获取一个客户端实例。当用Promise.all并行执行两个查询函数时,两个请求会同时向连接池申请客户端:
- 串行调用时,第一个函数执行完成并释放客户端后,第二个函数才会获取资源,不会触发等待;
- 并行调用时,若连接池的
max(最大连接数)配置过小,第二个请求会进入等待队列,当等待时间超过连接池的超时限制时,就会抛出连接超时错误。
2. 严重的SQL注入风险
你的查询使用字符串拼接WHERE email = '${email}'构造SQL语句,这会导致恶意用户通过构造特殊邮箱值执行任意SQL,直接威胁数据库安全。
解决办法
方案1:优化连接使用逻辑(推荐)
pg库推荐直接使用pool.query()方法,它会自动处理连接的获取与释放,无需手动管理客户端,从根源避免连接竞争问题,同时解决SQL注入风险:
改造db.service
移除手动获取客户端的connectDB函数,保留连接池实例即可:
import { Pool } from 'pg'; const dbConfig = {/* 你的有效数据库配置 */}; const pool = new Pool(dbConfig); export default pool;
改造查询函数
使用参数化查询替代字符串拼接,同时直接调用pool.query():
// fetchUser.js import pool from './db.service'; export const fetchUser = async (email) => { const query = ` SELECT full_name FROM app.users WHERE email = $1 `; try { const result = await pool.query(query, [email]); return result.rowCount ? result.rows[0].full_name : null; } catch (error) { console.error('查询用户失败:', error); throw error; // 不要吞掉错误,便于上层捕获处理 } };
// fetchWallet.js import pool from './db.service'; export const fetchWallet = async (email) => { const query = ` SELECT wallet_money FROM app.user_wallet WHERE email = $1 `; try { const result = await pool.query(query, [email]); return result.rowCount ? result.rows[0].wallet_money : null; } catch (error) { console.error('查询钱包失败:', error); throw error; } };
改造后再用Promise.all并行调用,连接池会自动分配可用资源,不会出现超时问题。
方案2:调整连接池配置(临时缓解)
如果暂时无法改造代码,可以增大连接池的最大连接数,并设置合理的超时时间:
const dbConfig = { // 原有配置 max: 20, // 根据业务并发需求调整,默认值为10 connectionTimeoutMillis: 5000 // 设置连接等待超时时间,单位毫秒 };
方案3:复用单个客户端(适用于事务场景)
如果需要在同一个连接中执行多个操作(比如事务),可以手动获取一次客户端,完成所有操作后再释放:
import pool from './db.service'; export const fetchUserAndWallet = async (email) => { const client = await pool.connect(); try { await client.query('BEGIN'); const userRes = await client.query('SELECT full_name FROM app.users WHERE email = $1', [email]); const walletRes = await client.query('SELECT wallet_money FROM app.user_wallet WHERE email = $1', [email]); await client.query('COMMIT'); return { fullName: userRes.rows[0]?.full_name, walletMoney: walletRes.rows[0]?.wallet_money }; } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); // 必须释放客户端回连接池 } };
内容的提问来源于stack exchange,提问作者Kgn-web
相关产品推荐
相关产品推荐

