正确做法是用 Eloquent 的 where('field', 'like', "%{$keyword}%") 实现安全模糊搜索,避免 whereRaw 手拼 SQL 导致注入;多字段需用闭包包裹防止 orWhere 优先级错误;空值参数必须用 filled() 显式校验;百万数据应弃用 LIKE 全模糊,改用 Scout+Meilisearch;分页需 withQueryString() 保留搜索参数。

用 whereLike 做基础模糊搜索,但别直接拼 SQL
Laravel 本身没有 whereLike 这个方法,常见错误是手写 whereRaw('name LIKE ?', ["%{$keyword}%"]) —— 看似能跑,但容易被 SQL 注入,尤其当 $keyword 来自请求时。正确做法是用 Eloquent 提供的 where 配合 LIKE 操作符,让框架自动转义:
Product::where('name', 'like', "%{$request->input('q')}%")->get()- 如果要忽略大小写(MySQL 默认不区分,PostgreSQL 区分),改用
ilike(仅 PostgreSQL)或统一转小写:whereRaw('LOWER(name) LIKE ?', [strtolower("%{$request->input('q')}%")]) - 多个字段同时模糊匹配?别堆
orWhere,用where包一层避免优先级问题:->where(function ($q) use ($k) { $q->where('name', 'like', "%$k%")->orWhere('desc', 'like', "%$k%"); })
多条件筛选时,空值参数必须显式跳过
用户没选分类、没填价格区间,但你把 category_id => null 或 min_price => '' 直接塞进查询,结果就是查出空数据或全表扫描。Laravel 不会自动忽略空值,得自己守门:
- 检查每个筛选字段是否「有意义」:
if ($request->filled('category_id')) { $query->where('category_id', $request->category_id); } -
filled()比has()更安全:它排除null、空字符串、空数组;has()只判断键是否存在 - 价格范围这类数字筛选,记得验证类型:
is_numeric($request->min_price),否则用户输字母会触发数据库报错SQLSTATE[HY000]: General error: 1366 Incorrect integer value
搜索性能掉得快?先看有没有加索引,再想要不要用 Scout
百万级数据下,LIKE '%keyword%' 必然走全表扫描,加普通 B-Tree 索引无效。这时候不是换写法就能解决的:
- 前缀匹配(
LIKE 'keyword%')可以走索引,但用户搜中间词就不行 - MySQL 5.7+ 支持全文索引,但只对 MyISAM/InnoDB 的
TEXT字段有效,且中文需配合ngram插件,配置麻烦 - 真正要支撑复杂搜索,Scout + Algolia/Meilisearch 是更现实的选择——但注意:Scout 默认同步模式,
save()会卡住响应;异步要用队列,且确保QUEUE_CONNECTION=redis或类似可靠驱动
分页 + 搜索组合时,withQueryString() 很关键
用户翻到第 3 页再改关键词,点完搜索却回到第 1 页,因为分页链接没保留原有参数。这不是 Bug,是默认行为:
- 在 Blade 中用
{{$products->withQueryString()->links()}},它会自动把当前 URL 查询参数(比如?q=phone&category=1)带进分页链接的href - 如果用了自定义分页视图,确认里面调用的是
$elements->appends(request()->query()),而不是空的appends([]) - 别在控制器里手动拼
->appends(['q' => $q]),漏掉其他筛选项就白干了
搜索逻辑看着简单,但参数校验、空值处理、索引策略、分页上下文这四块,漏掉任何一环都会在上线后突然卡住或返回错结果。











