设置async为true的jQuery Ajax请求致Web应用卡顿直至数据返回
问题:异步Ajax请求为何导致整个Web应用卡顿?
我正在维护一个别人开发的PHP Web应用,执行下面这段jQuery Ajax请求:
$.ajax({ type: 'POST', url: "/potato/ajax.php?module=test_module", dataType: 'json', async: true, data: { start_ts: that.start_date, stop_ts: that.end_date, submitted: true }, beforeSend: function() { console.log('Start: ' + new Date().toLocaleString()); // Show Chart Loading that.qwChart.showLoading({ color: '#00b0f0', text: that.qwChartProgress }); // If data div isn't displayed if (!that.dataDisplayed) { // Show divs loading that.showMainDiv(); } else { that.$qwTbody.slideUp('fast'); that.$qwTbody.html(''); } }, complete: function(){}, success: function(result){ console.log('End: ' + new Date().toLocaleString()); // Clear timer clearInterval(timer); // Set progressbar to 100% that.setProgressBarTo100(); // Show Download Button that.downloadBtn.style.display = 'inline-block'; // Insert Chart Data that.insertChartData(result); // Insert Table Data that.insertTableData(result); } });
按道理异步请求不该阻塞应用,但现在的情况是:提交Ajax请求后,刷新页面或并行打开应用内其他页面时,页面会白屏加载,直到Ajax请求返回数据后才正常显示。我特意手动设置了async: true确认,但问题依旧。这是服务端的问题吗?该怎么调试?
后端PHP处理代码如下:
<?php require_once("/www/common/api/db.php"); if (!empty($_POST['submitted'])) { // error_reporting(-1); // Users Array: $users = get_qw_data($start_ts_to_date, $stop_ts_to_date); // Summary Array: $summary = get_qw_summary($users); // QW Score Array: $qws = get_qw_score($users); // Generate CSV Report files /* Remove old:*/ if (!is_file_dir_exist($customer)) create_qw_directory($customer); /* Report #1: */ users_apps_google_macros_ma($users['users'], $customer); /* Report #2: */ usage_and_qw_summary($summary, $customer); /* Report #3: */ qw_score($qws, $customer); /* Zip Files: */ zip_qw_files($customer); echo json_encode($qws); }
分析与解决方案
兄弟,我太懂你这种困惑了——明明设了异步,怎么整个应用还是卡得像没开异步一样?答案几乎肯定在服务端的PHP会话锁上,这是PHP开发里非常容易踩的坑!
为什么会卡顿?
你说的“刷新页面/打开其他页面白屏”,本质不是前端异步请求的问题,而是PHP的session机制导致的:
- 默认情况下,PHP当一个请求启动会话(比如用
session_start(),或者框架自动启动)时,会锁住当前用户的session文件,防止多个请求同时修改session数据导致冲突。 - 这个锁会一直持有,直到请求完全结束(脚本执行完毕,或者主动调用
session_write_close()释放锁)。 - 你的Ajax请求触发了后端的PHP脚本,这个脚本里大概率启动了会话(比如
db.php里包含了会话初始化逻辑),而且脚本执行时间很长——你看后端代码里要生成多个CSV、压缩文件,这些都是耗时操作! - 这时候,同一个用户的其他请求(刷新页面、打开其他页面)都会因为等待session锁被释放而阻塞,表现出来就是页面白屏,直到Ajax请求的脚本执行完、锁释放,其他请求才能继续。
怎么调试验证?
- 确认会话锁的存在:
在后端PHP脚本的开头和关键位置添加日志,记录session_id()和当前时间,同时在另一个页面(比如首页)也添加同样的日志,然后同时触发Ajax请求和刷新首页,看首页的日志是不是在Ajax请求完成后才出现。也可以直接查看服务器上的session文件存储目录(通常是/var/lib/php/session/),用lsof命令查看哪个进程在占用对应session文件。 - 验证脚本执行时间:
在后端脚本开头加$start_time = microtime(true);,结尾加error_log("Script execution time: " . (microtime(true) - $start_time) . "s");,看看整个脚本跑多久——如果超过几秒,那肯定会导致明显卡顿。
解决办法
1. 尽早释放会话锁(最有效)
如果你的Ajax请求不需要修改session数据,只是读取或者根本不用session,那在脚本开头处理完session相关逻辑后,立刻调用session_write_close()释放锁:
<?php require_once("/www/common/api/db.php"); // 假设db.php里已经启动了session,现在立刻释放锁 session_write_close(); if (!empty($_POST['submitted'])) { // 后面的耗时操作都不会持有session锁了 $users = get_qw_data($start_ts_to_date, $stop_ts_to_date); $summary = get_qw_summary($users); $qws = get_qw_score($users); // ... 剩下的文件生成、压缩逻辑 echo json_encode($qws); }
这样后续的耗时操作就不会占用session锁,用户的其他请求可以正常处理,不会被阻塞。
2. 优化后端耗时操作
你的脚本里生成多个CSV、压缩文件都是IO密集型操作,可以优化:
- 把这些耗时操作放到异步任务队列里(比如用Redis队列+Worker进程),Ajax请求只触发任务,立刻返回结果,然后前端轮询或者用WebSocket通知任务完成。
- 优化文件生成和压缩的代码,比如用更高效的函数,避免不必要的IO操作。
3. 检查其他阻塞因素
- 看看后端脚本有没有执行长时间的数据库查询,比如
get_qw_data()是不是拉取了大量数据,没有加索引?可以用EXPLAIN分析查询语句。 - 服务器资源是不是不够?比如CPU、内存被占满,导致所有请求都变慢——可以用
top或htop命令查看服务器监控数据。
内容的提问来源于stack exchange,提问作者Imnotapotato
相关产品推荐
相关产品推荐

