Sequelize批量更新异常:仅最后30条数据成功更新问题排查
这绝对不是Sequelize的bug,问题出在你的代码逻辑和并发处理上,我帮你拆解一下:
1. map 遍历异步函数时未等待所有操作完成
你用 dataToBeInserted.map(async function(data) { ... }) 来批量执行upsert,但map并不会等待内部的异步函数完成——它会立刻生成一个由Promise组成的数组,然后主线程直接继续往下走(甚至可能在所有upsert完成前就结束进程)。
Sequelize默认的连接池大小是10(不同版本可能有所调整,比如部分版本是30),当你一次性触发数千个并发的数据库操作时,连接池会被占满,早期的请求会因为无法获取连接而被丢弃或者超时,只有最后一批能抢到连接完成更新。这就是为什么你看到所有日志都打印了,但只有最后30条生效。
2. 实例方法obj.update vs 模型方法csv.update的效率差异
你原来用的obj.update(values)是实例方法:它需要先通过findOne把整条记录加载到内存中,再执行更新,这个过程多了一次查询和实例化的开销,速度更慢。而csv.update(values, {where: condition})是模型方法,直接生成一条UPDATE SQL语句发送给数据库,没有额外的内存操作,执行效率高很多。
换成模型方法后,操作速度变快,在连接池的并发限制内完成了更多操作,所以看起来问题“解决”了,但本质的并发控制问题还是存在——如果数据量再大一些,依然会出现部分更新失败的情况。
方案一:用for...of顺序执行(最稳妥,适合数据量不是极大的场景)
把map换成for...of循环,确保每个upsert操作完成后再执行下一个:
try { await models.sequelize.authenticate(); await models.sequelize.sync(); let dataToBeInserted = getArrayOfDatatoInsert(); // 替换map为for...of for (const data of dataToBeInserted) { let condition = { 'categorygroup': data.categorygroup, 'category': data.category, 'country': data.country, 'city': data.city }; await csvModel.upsert(data, condition); } await restofthestuff(); } catch (error) { console.log("error", error); }
方案二:用Promise.all并行执行(适合数据量大,需提高效率的场景)
如果想要并行执行来提升速度,记得用Promise.all等待所有Promise完成,同时可以考虑限制并发数(避免连接池过载):
try { await models.sequelize.authenticate(); await models.sequelize.sync(); let dataToBeInserted = getArrayOfDatatoInsert(); // 用Promise.all等待所有异步操作完成 await Promise.all(dataToBeInserted.map(async (data) => { let condition = { 'categorygroup': data.categorygroup, 'category': data.category, 'country': data.country, 'city': data.city }; return csvModel.upsert(data, condition); })); await restofthestuff(); } catch (error) { console.log("error", error); }
额外优化:使用Sequelize内置的原子Upsert
你自己实现的upsert存在竞态条件:比如在findOne和create之间,可能有其他请求插入了相同条件的记录,导致重复插入。Sequelize内置了Model.upsert方法,它是原子操作(基于数据库的INSERT ... ON DUPLICATE KEY UPDATE语法),更安全高效:
// 替换你自己的upsert方法 csv.upsert = async function (values, condition) { // 合并条件和要更新的值,确保唯一键匹配 const upsertData = { ...values, ...condition }; // 指定需要更新的字段(排除条件字段,或者按需指定) return await csv.upsert(upsertData, { updateOnDuplicate: Object.keys(values) }); }
内容的提问来源于stack exchange,提问作者ro ko

