php 框架 sql 查询优化需五步:一、用 with()预加载防 n +1;二、用 scope 作用域复用 where 逻辑;三、用 db::raw()等嵌入安全原生片段;四、用 querylog 与 explain 分析瓶颈;五、以游标分页替代 offset 提升大数据性能。

如果您在使用 PHP 框架开发应用时发现 SQL 查询性能低下或代码冗余,可能是由于未充分利用查询构造器的高级特性。以下是优化 SQL 查询的具体步骤:
一、使用延迟加载与预加载避免 N + 1 查询问题
当遍历主模型并逐个访问关联数据时,框架可能为每条记录发起独立查询,造成大量数据库往返。通过预加载可将关联查询合并为单次或有限次数的 JOIN 或 IN 查询。
1、在 Eloquent 中,使用 with() 方法指定需预加载的关联关系,例如User::with('posts.comments')->get()。
2、对存在筛选条件的关联,使用闭包约束预加载,如User::with(['posts' => function ($query) {$query->where('status', 'published'); }])->get()。
立即学习“PHP 免费学习笔记(深入)”;
3、若需动态决定是否预加载,可在查询构建前用条件判断包裹 with() 调用,避免无谓的 JOIN 开销。
二、利用查询作用域复用常用 WHERE 逻辑
将高频出现的过滤条件(如软删除状态、租户 ID、时间范围)封装为本地或全局作用域,确保逻辑集中、调用简洁且不易遗漏。
1、在 Eloquent 模型中定义本地作用域方法,命名以 scope 开头,如public function scopeActive($query) {return $query->where('status', 'active'); }。
2、调用时直接链式使用,例如Post::active()->published()->get(),无需重复书写 WHERE 子句。
3、全局作用域需实现 apply() 方法并注册到 boot() 中,适用于全模型强制生效的规则,如多租户的 tenant_id 自动过滤。
三、手动编写原生片段嵌入查询构造器
当复杂计算、窗口函数或数据库特有语法无法通过链式方法表达时,可安全插入原生 SQL 片段,同时保留参数绑定能力防止注入。
1、使用 DB::raw() 包裹表达式,如select('users.*', DB::raw('COUNT(posts.id) as post_count'))。
2、在 whereRaw() 中传入带占位符的字符串及参数数组,例如whereRaw('DATE(created_at) = ?', [$date])。
3、对 GROUP BY 后 HAVING 条件,使用 havingRaw() 替代having(),支持聚合函数内嵌表达式,如havingRaw('SUM(amount) > ?', [1000])。
四、启用查询日志与执行计划分析
在开发环境捕获实际生成的 SQL 语句及执行耗时,结合数据库 EXPLAIN 输出识别索引缺失、全表扫描等瓶颈点。
1、调用 DB::enableQueryLog() 开启日志,之后用 DB::getQueryLog() 获取最近若干条记录。
2、对关键查询添加 dd(DB::table('orders')->toSql()) 查看未执行的 SQL 结构,确认绑定参数位置与逻辑正确性。
3、将生成的 SQL 复制至数据库客户端,前置 EXPLAIN 运行,检查 type 是否为 ref 或range,key列是否显示有效索引名。
五、分页优化:游标分页替代 OFFSET
传统 OFFSET 在大数据集下性能陡降,因数据库仍需扫描被跳过的全部行。游标分页基于上一页末尾记录的唯一有序字段值进行条件过滤,响应时间恒定。
1、选择一个严格单调递增或组合唯一且已建索引的字段作为游标,如 created_at 与id联合。
2、查询下一页时,使用 where('created_at', '>', $lastCreatedAt)->orWhere('created_at', '=', $lastCreatedAt)->where('id', '>', $lastId) 构造边界条件。
3、返回结果前,提取新一页首条记录的游标字段值,供前端下次请求携带,确保 ORDER BY 顺序与游标条件完全一致。






























