并行Ajax调用响应时序异常排查(PHP/Laravel后端)
我有一个基于PHP/Laravel后端、使用MySQL作为数据库的网站。近期遇到“Too many connections”问题,正在调试查找瓶颈。测试中发现某GET HTTP请求有时会重复触发(一分钟内数十次),这似乎拖慢了全局数据库查询处理,导致连接保持打开状态的时间大幅延长。不过,响应的时序让我感到十分意外。以下是从开发者控制台同时发起10次该请求后,Laravel调试日志的输出内容:Starting标记为刚进入控制器方法时,Returning标记为即将返回响应前。
[2019-07-30 09:02:09] dev.DEBUG: Starting query 1
[2019-07-30 09:02:17] dev.DEBUG: Returning from query 1 after 7.82s
[2019-07-30 09:02:18] dev.DEBUG: Starting query 2
[2019-07-30 09:02:26] dev.DEBUG: Returning from query 2 after 7.82s
[2019-07-30 09:02:26] dev.DEBUG: Starting query 3
[2019-07-30 09:02:29] dev.DEBUG: Starting query 4
[2019-07-30 09:02:29] dev.DEBUG: Starting query 5
[2019-07-30 09:02:29] dev.DEBUG: Starting query 6
[2019-07-30 09:02:30] dev.DEBUG: Starting query 7
[2019-07-30 09:02:31] dev.DEBUG: Starting query 8
[2019-07-30 09:02:45] dev.DEBUG: Returning from query 3 after 18.7s
[2019-07-30 09:02:46] dev.DEBUG: Starting query 9
[2019-07-30 09:02:56] dev.DEBUG: Returning from query 4 after 26.4s
[2019-07-30 09:02:56] dev.DEBUG: Returning from query 5 after 26.5s
[2019-07-30 09:02:56] dev.DEBUG: Returning from query 6 after 26.6s
[2019-07-30 09:02:56] dev.DEBUG: Returning from query 7 after 26.1s
[2019-07-30 09:02:57] dev.DEBUG: Returning from query 8 after 26.2s
[2019-07-30 09:02:57] dev.DEBUG: Starting query 10
[2019-07-30 09:03:02] dev.DEBUG: Returning from query 9 after 16.0s
[2019-07-30 09:03:06] dev.DEBUG: Returning from query 10 after 8.7s
请问对于这10个同时发起的调用,Laravel为何会先完整处理第一个,再处理第二个,之后并行处理3-8但3先完成,后续又呈现此类异常时序?
这种看似混乱的时序,其实是PHP-FPM进程调度、数据库连接池限制、MySQL锁竞争这几个环节共同作用的结果,我帮你逐个拆解:
PHP-FPM进程池的“排队启动”
Laravel依赖PHP-FPM来处理HTTP请求,而PHP-FPM的pm.max_children参数决定了服务器能同时运行的PHP进程数。如果你的配置里这个值偏小(比如默认的5-10),一开始服务器上只有1-2个空闲进程,所以前两个请求只能串行处理——等第一个请求执行完释放进程,第二个才能被分配到资源启动。等前两个请求完成后,进程池里的空闲进程变多,后续的3-8请求就能被分配到不同进程,实现并行启动。数据库连接池阻塞与锁竞争
请求3比4-8先完成,核心原因大概率是数据库层面的资源竞争:- 请求3可能刚好先拿到了数据库连接池的可用连接,而且它执行的SQL没有遇到锁等待(比如没有访问被其他请求占用的行/表),所以能顺畅完成;
- 而4-8请求可能因为连接池被占满,先花了一段时间等待可用连接,之后又因为访问了同一张表的同一数据,触发了MySQL的行锁/表锁,导致它们被阻塞在同一个等待队列里——直到请求3释放锁或者事务完成,这些被阻塞的请求才会几乎同时执行完毕,所以它们的返回时间几乎一致。
请求生命周期的连接占用
Laravel默认会在整个请求生命周期内保持数据库连接,直到请求结束才释放。如果你的请求里有长时间运行的操作(比如复杂的关联查询、未优化的SQL),连接会被长时间占用,进一步加剧连接池的紧张。前两个请求耗时7.8s,可能是当时数据库负载低、连接充足;而后续请求因为并发量上来,连接池资源不足,加上锁竞争,导致耗时大幅增加。后续请求的“补位”逻辑
你看到请求9在3完成后才启动、请求10在4-8完成后启动,这还是PHP-FPM进程池的限制——当3完成释放进程后,进程池才有空闲位置处理请求9;同理,4-8完成后释放多个进程,请求10才能被快速处理,所以它的耗时又回到了和前两个类似的水平。
调试建议
针对这个问题,你可以从这几个方向排查优化:
- 检查PHP-FPM配置:调整
pm.max_children、pm.start_servers等参数,确保进程池能容纳足够的并发请求; - 分析MySQL状态:执行
show processlist查看请求执行时是否有锁等待、慢查询,定位耗时的SQL; - 优化Laravel数据库操作:开启查询日志(
DB::enableQueryLog()),检查是否有重复查询、未加索引的SQL; - 确保事务及时提交:避免请求中存在未提交的事务,防止长时间占用数据库连接。
内容的提问来源于stack exchange,提问作者Buno




