forEach发起多个HttpClient Post时数据入库顺序错乱如何解决
问题根因
乱序和前端排序逻辑没有关系,核心原因是你在forEach循环里直接发起异步HTTP请求,所有请求是并发触发的:你只是按排序顺序依次触发了请求发送动作,但网络传输延迟、后端线程调度的差异是不可控的,先触发的请求可能后到达后端、后执行数据库写入,最终拿到的自增主键ID自然是随机的,和你预期的顺序不符。
另外你现有代码还有两个隐性bug:
- 反复调用
find遍历全量数据匹配CountMe,数据量大的时候性能很差 spinner的显示隐藏、最后一条数据的判断逻辑在并发场景下会错乱,比如第一个返回的请求刚好是最后一条触发的,就会提前清空表格,剩下的请求还在后台跑
修复方案
你可以根据实际情况二选一,优先选第一种方案,性能和可靠性都更好。
方案1:改后端为批量插入(推荐)
不要单条循环发请求,直接把排序好的整批日志一次性提交到后端,后端在单次请求、单个数据库上下文里按传入顺序执行写入,从根源上避免并发导致的乱序,同时请求量从N条降到1条,性能提升非常明显。
后端新增批量插入接口示例:
[HttpPost("batchinsertdtr")] [Authorize] public ActionResult<int> BatchInsertDtr(List<Dtr> models) { int result = 0; try { using (dbContext _dbcontext = new()) { // 严格按前端传入的顺序添加实体 foreach(var model in models) { _dbcontext.Dtrs.Add(new Dtr() { Empcode = model.Empcode, Logtype = model.Logtype, Trndate = model.Trndate, Trntime = model.Trntime, SvrTime = model.SvrTime }); } // 单SaveChanges会按添加顺序生成插入SQL _dbcontext.SaveChanges(); result = 1; } } catch (Exception ex) { Console.WriteLine(ex.Message); result = 0; } return result; }
前端对应修改,把所有日志处理完成后一次性提交即可,不需要循环发请求。
方案2:前端改为串行发送请求(不改后端的临时方案)
如果暂时没法修改后端接口,就把并发请求改成串行:等上一个请求写入成功返回之后,再发起下一个请求,保证请求的执行顺序和你排序的顺序一致。注意forEach不支持异步等待,要用普通for循环配合async/await实现。
前端修改后代码示例:
// 先直接对原数据按CountMe升序排序,不需要单独抽id再反复find const sortedLogs = this.allData.sort((a, b) => a.CountMe - b.CountMe); this.spinner.show(); let hasError = false; try { for (let i = 0; i < sortedLogs.length; i++) { const currentLog = sortedLogs[i]; const parseDateTime = new Date(currentLog.DateTimeRecord); const body = { empcode: currentLog.EmpIDNo, logtype: currentLog.InOut, trndate: moment(parseDateTime).format('YYYY-MM-DD'), trntime: moment(parseDateTime).format('HH:mm:ss').toString(), svrtime: moment(parseDateTime).format('YYYY-MM-DDTHH:mm:ssZ') } // 等待当前请求完成才会进入下一轮循环 const result = await this.apiservice.insertLogs(body).toPromise(); if (result !== 1) { this.toastr.error("Error occured!"); hasError = true; break; // 出错可以选择中断后续提交 } } if (!hasError) { $('#dtrTable').DataTable().clear().destroy(); this.allData = []; this.toastr.success("Successfully uploaded logs to server", '', { timeOut: 10000 }); } } catch (error) { this.toastr.error(error.message); console.log(error.message); } finally { this.spinner.hide(); }
额外注意事项
业务上不要强依赖数据库自增主键的顺序做判断。就算你保证了请求串行,也可能因为其他业务线的并发写入、事务回滚、数据库自增配置等问题,出现自增ID跳号、顺序不符合预期的情况。如果需要按日志生成顺序排序,直接把已有的
CountMe字段或者日志生成时间存到表里,用业务字段排序更可靠。
内容的提问来源于stack exchange,提问作者Rak
相关产品推荐
相关产品推荐

