最稳可控的查询构建方式是用db::table()初始化后显式调用select()和where()等方法,再以get()或paginate()触发执行;必须明确字段、条件及连接类型,避免全表扫描、id冲突和null判断错误。

直接用 DB::table() 初始化查询,再链式调用 where()、select() 等方法,最后用 get() 或 paginate() 触发执行——这是最稳、最可控的构建方式。别一上来就写 DB::table('users')->get(),漏掉条件或字段控制,线上容易扫全表甚至 OOM。
DB::table() 后必须显式控制字段和条件
Query Builder 不是“写完就跑”,它要求你手动点名要什么、筛什么。默认不加 select() 就是 SELECT *,不加 where() 就是全表扫描。
-
select()必须明确列出字段,尤其是多表join()时,避免id字段冲突,比如写成select('users.id as user_id', 'posts.id as post_id') -
whereNull('deleted_at')才等价于IS NULL;写成where('deleted_at', null)会生成= NULL,永远不命中 - 时间范围优先用
whereBetween('created_at', [$start, $end]),但得确认数据库字段类型是DATETIME还是TIMESTAMP,时区不一致会导致查不到数据
动态条件别堆 if,用 when() 更干净
多个可选筛选参数(比如搜索关键词、状态、分类)堆 if 容易嵌套深、难维护。when() 是专为此设计的,条件为真才执行闭包里的逻辑,且返回原查询实例,天然支持链式。
$query->when($request->filled('q'), fn ($q) => $q->where('name', 'like', "%{$request->q}%"))-
when()第三个参数可传default闭包,用于“条件不满足时执行什么”,比如补个默认排序:->when(!$request->has('sort'), fn ($q) => $q->orderBy('id', 'desc')) - 注意:不要在
when()里写$query->xxx,闭包参数是新实例,应操作$q
多表关联慎用 join(),注意 INNER vs LEFT
join() 默认是 INNER JOIN,主表记录若在关联表无匹配,整行就被过滤掉。想保留主表全部数据,必须显式写 leftJoin()。
- 字段重名问题:两个表都有
id,select('*')会导致 PHP 数组里只留一个id,必须用别名,如select('users.id as user_id', 'profiles.id as profile_id') -
with()在 Query Builder 里不可用,它是 Eloquent 模型专属方法,DB::table()调用会报错Call to undefined method ...::with() - 如果只是查列表+展示关联字段(如订单+用户昵称),用
join()一次查平;如果后续还要对关联模型做复杂过滤或复用作用域,才考虑 Eloquent +with()
分页和性能陷阱:paginate() 不等于安全
paginate(15) 看似简单,但它底层会先执行一条 SELECT COUNT(*) —— 如果没给 WHERE 条件加索引,或者 COUNT 字段本身无索引,这条语句可能卡死。
-
limit(15)->offset(3000)表面跳过前 3000 条,MySQL 实际仍要扫描并丢弃这 3000 行,数据量大时极慢 - 真正轻量的方案是游标分页:
where('id', '>', $lastId)->limit(15),前提是排序字段有索引且单调递增 - 大数据导出别用
get()全拉进内存,改用chunk()分批处理,避免 OOM
最常被忽略的是字段控制与连接类型——select() 漏写、join() 忘加 left、whereNull() 写成 where(..., null),这几个点在线上几乎必出问题,而且不好排查。











