thinkphp中where('name','like','%关键词%')慢是因为mysql对前导通配符(%关键词%)无法使用索引,导致全表扫描;应改用前缀匹配、全文索引或应用层处理。

ThinkPHP 里用 where('name', 'like', '%关键词%') 慢,不是框架问题,而是 MySQL 的 LIKE 前导通配(%关键词%)直接让索引失效——哪怕你给 name 字段建了索引,也白搭。
为什么 LIKE 带前导 % 就查得慢
MySQL 只能利用索引做「左前缀匹配」,LIKE '关键词%' 能走索引,LIKE '%关键词%' 必须全表扫描。实测百万级用户表,WHERE name LIKE '%张三%' 耗时从 12ms 涨到 1800ms+,EXPLAIN 显示 type: ALL。
- 复合索引对
LIKE无效:即使有(status, name)联合索引,WHERE status=1 AND name LIKE '%张%'依然全表扫 -
fulltext索引不兼容 ThinkPHP 默认查询:Db::where('name', 'match', '张三')不被原生支持,需手写原生 SQL - 字符集影响更大:如果
name是utf8mb4+utf8mb4_unicode_ci,模糊匹配还会额外做排序转换,进一步拖慢
替代 LIKE '%xxx%' 的三种可行方案
别硬扛,换思路。以下方案按落地成本从低到高排列:
- 前端加限制:搜索框加「至少输入 2 个字」校验,后端再用
WHERE name LIKE '张三%'—— 这招解决 70% 场景,且能走索引 - 用
INSTR()或LOCATE()函数(MySQL):Db::whereRaw('INSTR(name, ?) > 0', [$keyword])->select(),比LIKE '%...%'略快但仍是全表扫,仅适合低频或小表 - 上
fulltext+ 原生 SQL:Db::query("SELECT * FROM user WHERE MATCH(name) AGAINST(? IN NATURAL LANGUAGE MODE)", [$keyword]),需提前建FULLTEXT(name)索引,且只支持自然语言模式或布尔模式,不能精确控制分词
ThinkPHP 中正则查询(REGEXP)的坑
别轻易用 where('name', 'regexp', '^[A-Z].*')。MySQL 的 REGEXP 不走任何索引,纯内存逐行匹配,数据量一过万就卡死。
- 正则表达式越复杂,CPU 消耗越高:比如
[a-z]+@[a-z]+\.[a-z]{2,}在 10 万行邮箱字段上平均耗时 2.3s - ThinkPHP 不会对
regexp条件做参数绑定预编译,SQL 拼接风险更高 - 真正需要正则时,先用
strpos()或str_starts_with()做粗筛:if (strpos($row['name'], $prefix) !== false) { /* 再进正则细筛 */ }
模糊查询该不该放数据库里做
答案很现实:95% 的场景不该。把模糊逻辑搬到 PHP 层或搜索引擎更可控。
- 小数据量(id,name,用
array_filter()+stripos()在内存筛,比数据库慢查询还快 - 中等数据量(1–50 万):引入
sphinx或elasticsearch,用 ThinkPHP 的Db::query()直连,避免 ORM 封装损耗 - 高频模糊搜索(如后台用户检索):必须加缓存层,且 key 要包含搜索关键词哈希,例如
cache('user_search_' . md5($keyword), $result, 60)
最常被忽略的一点:模糊查询往往和分页共存,而 paginate() 的 COUNT(*) 在模糊条件下会重复执行全表扫描——这时候游标分页(WHERE id > ? ORDER BY id LIMIT 20)比传统分页省掉一半时间。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











