Node.js使用Promise.all执行PostgreSQL批量插入耗时过长问题排查
性能问题根因
- 单条插入的往返开销过高:现有代码每条数据都单独调用
insertData发起插入请求,每次请求都包含网络往返、事务提交(如果insertData内部默认自动提交事务)的固定开销,5000次独立请求的累计开销是性能差的核心原因,和Promise.all本身的异步逻辑无关。 - 并发无控导致连接排队:直接将所有插入任务通过
Promise.all一次性提交,当任务量超过数据库连接池的最大连接数时,多余任务会在Node.js侧排队等待空闲连接,进一步拉长耗时。
优化方案
优先方案:改用批量插入
将多条待插入数据拼接为单条批量插入SQL,一次性提交请求,可将整体耗时降低99%以上。PostgreSQL批量插入语法示例:
INSERT INTO 目标表 (id, campaign, 邮件相关字段) VALUES ($1, $2, $3), ($4, $5, $6), ...
代码改造示例:
// 新增批量插入方法 async function batchInsert(connector, insertList) { const values = [] const placeholders = [] // 按实际字段数量调整偏移量,以下按3个字段示例 insertList.forEach((item, index) => { const offset = index * 3 placeholders.push(`($${offset + 1}, $${offset + 2}, $${offset + 3})`) values.push(item.id, item.campaign, item.email) }) const sql = `INSERT INTO 你的表名 (id, campaign, email) VALUES ${placeholders.join(',')}` return connector.query(sql, values) } // 改造业务逻辑 async function sendAndInsert(payload) { const allInsertData = [] // 先收集所有待插入数据 for (const data of payload) { for (const email of data) { allInsertData.push({ id: data.id, campaign: data.campaign, email: email // 补充你需要的其他邮件相关字段 }) } } // 按批次插入,避免单条SQL过长,批次大小可根据实际场景调整为500-2000 const BATCH_SIZE = 1000 for (let i = 0; i < allInsertData.length; i += BATCH_SIZE) { const currentBatch = allInsertData.slice(i, i + BATCH_SIZE) await batchInsert(connector, currentBatch) } }
改造完成后插入5000条数据的耗时可控制在10秒以内。
备选方案:必须单条插入时的优化
如果业务逻辑要求必须单条插入(比如每条插入需要独立校验、错误处理),可做如下优化:
- 控制并发数:使用并发池控制同时执行的插入任务数量,并发数和数据库连接池的最大连接数保持一致即可,避免无意义的排队。
- 复用事务:将同一批次的插入任务放在同一个事务中执行,避免多次事务提交的开销。
额外注意事项
原有代码中dataToInsert在内层循环外定义,如果你insertData内部没有做对象深拷贝,可能会出现所有插入数据都引用同一个对象的逻辑错误,建议调整为每次循环生成新的数据对象。
内容的提问来源于stack exchange,提问作者digitai
相关产品推荐
相关产品推荐

