Knex事务中批量更新操作耗时逐次递增问题咨询
事务中批量更新耗时递增的原因与优化方案
问题现象
在事务中批量执行更新操作时,日志显示后续更新的耗时逐次增加,单次耗时从129ms开始,每次递增约129ms,最终总耗时达到1745ms。
原代码示例
const updateFuncs = data .map(async (item) => { const now11 = new Date().getTime() let entity = this.removeUnusedKeys(item) entity = delUndefinedOfKeys(entity) const { id, ...rest } = entity const now22 = new Date().getTime() if (Object.keys(rest).length !== 0) { const query = trx(this.tabName) .where({ id }) .update(rest) .then((res) => { console.log(new Date().getTime() - now22, `each =======${id}`) }) return query } return null }) .filter(Boolean) await Promise.all(updateFuncs) await trx.commit()
耗时日志
129 each =======10532 259 each =======10533 398 each =======10534 526 each =======10535 651 each =======10536 798 each =======10537 930 each =======10538 1061 each =======10539 1196 each =======10540 1347 each =======10541 1475 each =======10542 1615 each =======10543 1745 each =======10544
原因分析
计时逻辑的误导:日志中计算的耗时是从
now22(实体处理完成的时间)到查询完成的时间,而非单个查询的实际执行时间。由于事务内的更新被数据库串行处理,后续查询需要等待前面的所有查询执行完毕才能开始,因此这个耗时包含了前面所有查询的执行时间总和,导致看起来逐次递增。事务内查询的串行执行:为了保证事务的ACID特性(原子性、一致性、隔离性、持久性),多数数据库对同一事务内的多个DML操作不会并行执行,而是按请求顺序排队处理。即使通过
Promise.all同时发起所有更新请求,数据库也会逐个执行,造成后续查询的等待时间累加。
解决方案
1. 修正计时逻辑,查看真实执行时间
将计时点移到查询执行前,这样能准确看到单个查询的实际耗时:
const updateFuncs = data .map(async (item) => { let entity = this.removeUnusedKeys(item) entity = delUndefinedOfKeys(entity) const { id, ...rest } = entity if (Object.keys(rest).length !== 0) { const queryStart = new Date().getTime() // 移到查询前计时 const query = trx(this.tabName) .where({ id }) .update(rest) .then((res) => { console.log(new Date().getTime() - queryStart, `each =======${id}`) }) return query } return null }) .filter(Boolean) await Promise.all(updateFuncs) await trx.commit()
修正后,你会发现每个查询的实际执行时间基本一致,不会出现递增的情况。
2. 批量更新优化(推荐)
将多个更新合并为一个批量查询,大幅提升性能。以Knex为例,可以使用batchUpdate方法:
// 先整理需要更新的数据 const updateData = data .map(item => { let entity = this.removeUnusedKeys(item) entity = delUndefinedOfKeys(entity) const { id, ...rest } = entity return Object.keys(rest).length ? { id, ...rest } : null }) .filter(Boolean) // 执行批量更新 if (updateData.length) { await trx(this.tabName) .batchUpdate('id', updateData, { chunkSize: 100 }) // chunkSize可根据数据库调整 } await trx.commit()
批量更新只需与数据库建立一次连接,执行一次SQL语句,性能远优于循环单个更新。
内容的提问来源于stack exchange,提问作者QQ.FE
相关产品推荐
相关产品推荐

