pg-promise执行先查询后插入操作时报Client was closed错误如何解决
错误产生原因
- 核心原因是异步任务没有被正确纳入pg-promise的连接生命周期管理:你在
then回调内发起的t.tx()没有使用return返回对应的Promise对象,导致外层的t.batch()无法感知到这部分异步操作的执行状态,t.batch()完成所有可追踪的查询后直接释放了task占用的数据库连接,后续还在执行的t.tx()拿不到可用连接就抛出该错误。 - 你的业务逻辑存在冗余错误:你先全量查询了
sf_data表所有数据,再循环每条数据重复查询ID是否存在,完全是无意义的重复IO,且和你「校验samplePayload中的数据是否存在」的需求逻辑相悖。 - 初始版本的SQL拼接写法存在SQL注入风险,虽然你后续改成了参数化查询是正确的,但逻辑链路本身走偏了。
修复方案
快速修复现有代码的报错
只需要给t.tx()加上return,让它的Promise被外层queries数组收集,t.batch()就会等待所有事务执行完成再释放连接:
const queries = sfdata.map((sp) => { return t.oneOrNone('select * from sf_data where id = ${id}', sp).then(result => { console.log(result) if(result){ // 这里加return,把事务Promise返回给上层 return t.tx(async t2 => { return t2.none("insert into test_sf values ($1, $2, $3)", [uuidv1(), result.id, result.sfid]); }) } }); }); return t.batch(queries);
更合理的批量校验插入实现(推荐)
你的需求完全可以用PG原生的INSERT ... ON CONFLICT语法实现,不需要先查再插,没有并发问题,性能提升非常明显,也不会出现连接提前释放的问题:
const { helpers } = require('pg-promise')(); // 组装你要插入的批量数据,对应test_sf表的字段 const dataToInsert = samplePayload.map(sp => ({ id: uuidv1(), sf_data_id: sp.id, sfid: sp.sfid })); // 生成批量插入语句,指定sf_data_id为冲突键,冲突时不执行插入 const insertQuery = helpers.insert(dataToInsert, ['id', 'sf_data_id', 'sfid'], 'test_sf') + ' ON CONFLICT (sf_data_id) DO NOTHING'; // 直接执行单条查询完成批量校验插入 await db.none(insertQuery);
如果你的冲突判断逻辑更复杂,也可以先一次性查所有samplePayload的ID是否存在,过滤出不存在的ID再批量插入,避免循环查询:
return db.task(async t => { // 一次性查所有待校验的ID,不需要循环查库 const existsIds = await t.map('SELECT id FROM sf_data WHERE id IN ($1:csv)', [samplePayload.map(i => i.id)], a => a.id); // 过滤出不存在的ID const notExistsData = samplePayload.filter(sp => !existsIds.includes(sp.id)); // 批量插入不存在的数据 if(notExistsData.length) { const insertData = notExistsData.map(sp => [uuidv1(), sp.id, sp.sfid]); await t.none(helpers.insert(insertData, ['id', 'sf_data_id', 'sfid'], 'test_sf')); } });
内容的提问来源于stack exchange,提问作者Ronaldo Cristover
相关产品推荐
相关产品推荐

