必须设置pdo连接超时和sql执行时限:在config/database.php中添加pdo::attr_timeout=>5;api请求用中间件设max_execution_time=2000ms;预加载前按id数量降级嵌套深度。

当 Laravel 嵌套预加载(如 with('user.posts.comments.user'))在高并发或数据量大时触发 MySQL 连接超时、PHP 执行超时或内存耗尽,必须在不改架构的前提下快速干预查询生命周期。
设置数据库连接级超时阈值
在 config/database.php 的对应数据库连接配置中,添加 'options' => [PDO::ATTR_TIMEOUT => 5],强制 PDO 层在 5 秒内中断等待。
这一步必须写在 mysql 或 pgsql 连接数组内部,【不加此配置,后续所有超时控制都可能被底层连接卡死】。
若使用 MySQLi 驱动,则需改用 'options' => [MYSQLI_OPT_CONNECT_TIMEOUT => 5],驱动不匹配会导致配置静默失效。
为单次嵌套查询设置执行时限
方法一:用 DB::statement() + SET SESSION MAX_EXECUTION_TIME
在查询前执行原生语句:DB::statement("SET SESSION MAX_EXECUTION_TIME = 3000");,单位毫秒,仅对当前连接生效。
方法二:用 DB::select() 封装带超时的子查询
将深层嵌套逻辑拆出为独立子查询,用 DB::select() 手动执行,并配合 try/catch 捕获 Illuminate\Database\QueryTimeoutException。
注意:Laravel 10+ 才原生支持该异常类,低版本需监听 PDOException 并判断错误码是否为 1969(MySQL 超时码)。
用 timeout() 中间件拦截慢查询请求
第一步:运行 php artisan make:middleware QueryTimeout
第二步:在 handle() 方法中插入以下逻辑:
if (request()->is('api/*') && !app()->environment('local')) {
DB::connection()->getPdo()->exec("SET SESSION MAX_EXECUTION_TIME = 2000");
}
第三步:将中间件注册到 app/Http/Kernel.php 的 $middlewareGroups['api'] 数组末尾。
这一步会覆盖全局连接配置,确保 API 请求一律走 2 秒硬限制,避免某次慢查询拖垮整个连接池。
预加载前主动降级嵌套深度
① 先查主模型 ID 列表,用 pluck('id') 获取纯数组,不触发任何关联加载。
② 检查 ID 数量:若 count($ids) > 50,则跳过 comments.user 这类三级嵌套,只保留 with(['user', 'posts'])。
③ 对剩余小批量数据(≤50 条),再调用 $models->load('posts.comments.user') 分批补载。
这一步规避了大集合下 with() 生成的巨量 IN 子句和笛卡尔积膨胀,实测可将 12s 查询压至 800ms 内。











