多字段搜索需主表条件放外层、关联条件放whereHas闭包内,避免混用;动态条件用when时需显式判断null;模糊搜索应配全文索引并改用match against;字段类型须与数据库严格一致,否则索引失效。

whereHas 多对多关系搜索字段写错就查不到
多字段搜索常涉及关联表,比如用户搜索“名字含张、订单状态为已完成、商品类目是数码”,这时 whereHas 是绕不开的。但很多人直接在闭包里用 where 混合主表字段,结果查出空集——因为 whereHas 只作用于关联模型,主表条件得放在外层。
- 错误写法:
->whereHas('orders', function ($q) { $q->where('status', 'done')->where('users.name', 'like', '%张%'); })(users.name在 orders 表里根本不存在) - 正确拆分:主表条件放外层,关联条件放闭包内,例如
->where('name', 'like', '%张%')->whereHas('orders', function ($q) { $q->where('status', 'done'); }) - 如果要关联字段也模糊匹配(比如订单备注),只能用
whereHas闭包里的where,不能用whereRaw直接拼表名,除非显式 join
when 方法动态拼 where 条件别漏掉 null 判断
when 是 Laravel 动态构建查询最常用的方式,但它只在第一个参数「为真」时执行闭包。问题在于 PHP 的 empty('0') 是 true,isset(null) 是 false,但表单传过来的空字符串、'0'、null 都可能被当成“无筛选”。
- 安全写法是显式判断:
->when(request('keyword'), fn ($q, $v) => $q->where('title', 'like', "%{$v}%"))—— 这里request('keyword')为null或空字符串时自动跳过 - 但如果字段允许值为
'0'(比如状态码),就得改成->when(!is_null(request('status')), ...) - 多个字段组合时,每个
when独立判断,不要塞进一个闭包里做逻辑合并,否则可读性差且难调试
全文索引没配,like '%关键词%' 查询一慢就超时
当数据量上万,又依赖 like '%xxx%' 做多字段模糊搜,MySQL 默认走全表扫描。加普通索引没用,因为前导通配符会失效。
- MyISAM 引擎支持
FULLTEXT,InnoDB 从 5.6+ 也支持,建索引语句是ALTER TABLE posts ADD FULLTEXT(title, content); - Laravel 迁移里用
$table->fullText(['title', 'content']);,但注意它只生成 DDL,不自动改查询逻辑 - 启用后得改写查询:用
match(title,content) against(? in natural language mode),而不是where('title', 'like', ...);Laravel 没封装这个,得手写whereRaw - 中文分词需额外配置 MySQL 的 ngram parser,否则单字搜效果差
搜索字段类型不一致导致隐式转换和索引失效
常见坑是数据库字段是 VARCHAR,但代码里传了整数或布尔值过去,比如 ->where('status', request('status')),而表单提交的 status=1 是字符串,MySQL 可能转成数字比对,触发全表扫描。
- 始终确保传入值类型与字段定义一致:整型字段用
(int) request('status'),枚举字段用in_array()校验后再进查询 - 时间范围搜索尤其危险:
whereBetween('created_at', [request('start'), request('end')]),如果传的是 '2024-01' 这种不完整格式,MySQL 会转成0000-00-00,索引完全失效 - 用
toSql()和dd()打印最终 SQL,看绑定参数是不是你预期的类型,比猜快得多
多字段搜索真正卡住人的地方,往往不是语法不会写,而是字段语义没理清、数据类型没对齐、或者压根忘了索引要重设。上线前拿 10 万条测试数据跑一遍 explain,比看十遍文档都管用。


















