如何使用Ajax与jQuery传输大数据量并优化批量入库与大文件上传性能
现有实现的核心性能瓶颈
- 单条数据触发独立HTTP请求:1万条数据对应1万个POST请求,受浏览器同域名并发连接数硬限制(通常6-8个),请求会在浏览器端形成排队队列,随着连接占用不释放,越到后期队列堆积越严重,就会出现明显降速
- 事件重复绑定:
#clear元素的click事件被写在setInterval的执行逻辑里,每100ms执行一次就多绑定一次同样的处理逻辑,运行1000次就绑定了1000次,一次点击会触发上百次重复清空操作,内存占用持续上涨 - 定时触发逻辑无等待:setInterval不管上一次AJAX有没有收到响应,到点就强制发新请求,实际请求发送速度远超出浏览器和服务器的处理能力,队列越攒越多
- 无节制DOM渲染:每收到一个响应就往
#result容器append一个新DOM节点,同时触发一次滚动动画,1万次操作就会生成1万个DOM节点,页面渲染压力会随节点数增长指数上升 - 数据库操作开销过高:单条请求单独建立数据库连接、执行一次INSERT、提交一次事务,1万次流程的连接和事务提交开销就会吃掉90%以上的运行时长
落地优化方案
前端代码优化
- 改用分批传输替代单条请求:将前端解析完成的结构化数据按固定大小切分批次(比如每50条为一组),一次性把整组数据作为请求体发给后端,1万条数据仅需200次请求,直接把HTTP请求总量降低98%
- 替换定时触发为递归请求:等上一批请求的响应完全接收、处理完成后,再触发下一批请求的发送,从根源上避免请求队列堆积
- 事件绑定前置到初始化阶段:把
#clear的点击逻辑只绑定一次,不要再嵌套在循环/定时逻辑内部,杜绝重复绑定导致的内存泄漏 - 批量DOM渲染:把每个批次的处理结果先拼接成完整HTML片段,一次性插入到结果容器中,最后统一做一次滚动到底的动画,把DOM操作次数从1万次降到几百次
优化后的前端参考代码:
$(document).ready(function () { // 全局任务状态标识 let isPaused = false; const BATCH_SIZE = 50; // 可根据实际网络情况调整每批大小 let currentPos = 0; let totalSuccess = 0; let totalFail = 0; let parsedRows = []; let sharedParams = {}; // clear按钮绑定只执行一次 $('#clear').click(function () { isPaused = true; $('#title').val(''); $('#date').val(''); $('#csvfile').val(''); $('#result').empty(); $(".loader").css('display','none'); }); $('#csv').on('submit',function (e) { e.preventDefault(); // 保留原有表单校验逻辑 // ... 原校验代码块 const file = document.getElementById('csvfile').files[0]; const reader = new FileReader(); reader.readAsText(file); reader.onload = function(event){ // 保留原有CSV前端解析逻辑,解析完成的行数据存入parsedRows // ... 原CSV解析代码块 parsedRows = data; currentPos = 0; totalSuccess = 0; totalFail = 0; isPaused = false; sharedParams = {title: $('#title').val(), date: $('#date').val()}; $('.loader').css('display','inline-block'); // 启动批次请求 sendNextBatch(); } }); // 递归发送批次请求,不占用多余并发 function sendNextBatch() { // 任务终止条件 if (isPaused || currentPos >= parsedRows.length) { $(".loader").css('display','none'); $('#result').append('<span class="success">Fin d\'execution du script</span>'); return; } // 截取当前批次数据 const currentBatch = parsedRows.slice(currentPos, currentPos + BATCH_SIZE) .map(item => ({...item, ...sharedParams})); $.ajax({ url: "/admin/add/action/", type: "POST", data: {batch_payload: JSON.stringify(currentBatch)}, cache: false, dataType: "json", success: function (resp) { // 拼接整批的HTML片段一次性插入 let fragment = ''; resp.row_results.forEach((row, idx) => { const globalIndex = currentPos + idx + 1; fragment += `<span class="${row.color}"> n° ${globalIndex}<br> ${row.msg} </span>`; row.color === 'success' ? totalSuccess++ : totalFail++; }); $('#result').append(fragment); // 统一滚动到底部 $('#result').animate({ scrollTop: $('#result')[0].scrollHeight }, 50); // 指针移到下一批,继续发请求 currentPos += BATCH_SIZE; sendNextBatch(); }, error: function () { $(".loader").css('display','none'); $('#result').append(`<span class="error">网络异常,任务暂停。已成功导入${totalSuccess}条,失败${totalFail}条</span>`); } }); } });
后端逻辑优化
- 接收到前端传的批次JSON数据后,不要逐条建立数据库连接、逐条提交事务:在同一个数据库连接、同一个事务内,执行批量INSERT语句完成整批数据的写入,把数据库操作的开销降到最低
- 逐行完成数据校验和写入后,把每行的处理结果(成功/失败状态、对应提示文案)整理成数组,一次性返回给前端即可
- 针对之前反馈的大CSV上传慢的问题:这套方案是在浏览器端完成CSV全量解析,根本不需要把原始CSV文件上传到服务器,仅需要传解析后的结构化JSON数据,1万条数据对应的传输体积通常仅几百KB,完全不存在大文件等待上传过长的问题
优化效果对比
- 原方案:1万条数据对应1万个请求,单条独立数据库操作,1万次DOM渲染,整体运行时长远超合理范围,后期请求堆积会出现明显卡顿
- 优化后:1万条数据对应约200次请求,整批批量数据库写入,200次批量DOM渲染,整体运行速度平稳,没有请求堆积导致的降速问题,整体运行时长可降到原方案的1/20以下
内容的提问来源于stack exchange,提问作者Nat Dor
相关产品推荐
相关产品推荐

