应使用 with() 预加载避免 n+1 查询,慎用 get()/all(),优先 paginate() 和 chunkbyid(),用 selectraw() 下推计算至数据库。

避免 N+1 查询:用 with() 预加载代替循环中查模型
最常见的性能杀手不是 SQL 慢,而是发了几十次小查询。比如遍历用户列表时,每个用户都调用 $user->posts——这会触发 N 次 SELECT * FROM posts WHERE user_id = ?。
正确做法是提前把关联数据一次性捞回来:
Users::with('posts')->get();
注意点:
-
with()只对 Eloquent 关系有效,不能用于普通字段或手动 join 的结果 - 嵌套预加载写成
with(['posts.comments', 'profile']),但别过度嵌套,三层以上容易内存暴涨 - 如果只想要关联的某些字段,用
select()限制:with(['posts' => fn ($q) => $q->select('id', 'title', 'user_id')]) - 用
toSql()看生成的 SQL,确认是否真合并成了 JOIN 或独立子查询
慎用 get() 和 all():先加约束再取数据
写 User::get() 前,得问自己:真要全表扫描?哪怕只有 100 行,它也会绕过索引、不走缓存、无法分页。
常见误用场景:
- 只取一个用户却写
User::get()->first()—— 改成User::first()或User::find($id) - 统计数量写
User::get()->count()—— 改成User::count(),走 COUNT(*) 而非拉全量再 PHP 计数 - 分页前先
get()再手动切数组 —— 直接用paginate()或simplePaginate()
额外提醒:all() 是静态方法,不支持链式条件,且会忽略全局作用域(globalScopes),基本没理由用。
chunkById() 替代 chunk() 防止死锁和偏移失效
批量处理几万条记录时,chunk(1000) 看似简单,但它靠 OFFSET 实现,在高并发或写入频繁的表上极易卡住,且跳过已删行后页码会错乱。
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
chunkById() 用主键范围查询,更稳定:
User::chunkById(1000, function ($users) {
foreach ($users as $user) {
// 处理
}
});
注意点:
- 必须有带索引的整型主键(
id),UUID 或字符串主键不适用 - 不能配合
orderBy()自定义排序,它内部按id ASC固定推进 - 如果业务需要按创建时间处理,可建联合索引
(created_at, id),再手写带WHERE created_at > ? AND id > ?的分页逻辑
用 selectRaw() 和原生表达式减少 PHP 层计算
像“查最近 7 天注册用户数”这种需求,别在 PHP 里 filter() 或 foreach 判断时间,让数据库直接算好。
示例:
User::selectRaw('DATE(created_at) as date, COUNT(*) as count')
->where('created_at', '>=', now()->subWeek())
->groupBy('date')
->get();
关键判断:
- 日期函数(
DATE()、YEARWEEK())务必确认 MySQL 版本支持,低版本可能不识别DATE_TRUNC() -
selectRaw()不做参数绑定,拼接变量时必须手动过滤,否则 SQL 注入风险极高 - 聚合字段名如
count会被 Laravel 自动转成小写,PHP 中用$row->count取值,别写成$row->COUNT
复杂计算别硬塞进 Eloquent,该写视图或存储过程就写,ORM 不是万能胶水。










