查得慢八成是没走索引,而非代码问题;须用getLastSql()获取真实SQL并EXPLAIN验证key是否非NULL,警惕隐式转换、函数操作、OR、左模糊等导致索引失效的写法。

查得慢,八成是没走索引,而不是代码写得差。 ThinkPHP 本身不建索引、不改执行计划,它只负责把你的 where() 翻译成 SQL;真正决定快慢的,是 MySQL 拿到那条 SQL 后怎么执行。所以优化第一反应不该是“换写法”,而是先确认:这条 SQL 走索引了吗?
怎么确认 SQL 到底有没有走索引
不能光看 buildSql() 或日志里带问号的 SQL 字符串——那只是“长得像”的语句,不是真实执行的。必须拿到真实参数替换后的 SQL,丢进 MySQL 执行 EXPLAIN 看执行计划。
-
getLastSql()比buildSql()更可靠,它返回已填充参数的完整 SQL(如SELECT * FROM user WHERE user_id = 123) - 在 MySQL 客户端执行
EXPLAIN SELECT * FROM user WHERE user_id = 123,重点盯三列:key(非 NULL 才算走了索引)、rows(越接近实际结果数越好)、Extra(出现Using filesort或Using temporary就得重写) - 常见假象:
user_id字段明明建了索引,但EXPLAIN显示key是NULL——大概率是类型不匹配,比如数据库里是VARCHAR,PHP 传的是整数123,MySQL 隐式转换导致索引失效
哪些 ThinkPHP 写法会直接让索引失效
ThinkPHP 的链式调用看着干净,但一写错,MySQL 就自动放弃索引。这些不是 bug,是 SQL 层面的硬限制。
-
where('name', 'like', '%abc'):左模糊,B+ 树没法从头匹配,必然全表扫 -
whereRaw('YEAR(create_time) = 2024'):对字段做函数操作,索引失效;应改写为whereBetweenTime('create_time', '2024-01-01', '2024-12-31') -
where('status', '>', 1)->where('score', '>', 80):复合索引中,status是范围查询,后面字段score无法利用索引顺序 -
where('user_id', $uid)->order('id desc'):如果只有user_id索引,没有id索引或联合索引,排序仍触发filesort
分页和关联查询最容易踩坑的地方
这两个功能默认行为看似省事,实则暗藏性能雷区,尤其数据量上来后,响应时间会断崖式上涨。
立即学习“PHP免费学习笔记(深入)”;
-
paginate()默认先执行SELECT COUNT(*),带JOIN或复杂WHERE时,这个COUNT本身就成了瓶颈;大表建议改用游标分页:where('id', '>', $lastId)->limit(20) -
with('profile')是 N+1 查询,不是 JOIN;真要联查,得用join()显式写出,否则EXPLAIN看不到 JOIN 类型,优化无从下手 -
with()不加field(),主模型 + 关联模型全字段叠加,网络传输、内存占用、JSON 序列化全拉胯;必须在关联定义里写死->field('user_id,real_name')或用闭包控制 -
whereIn('id', $hugeArray)超过 1000 个元素,MySQL 可能拒绝执行或退化成全表扫描;得手动分批,比如每 500 个一组循环查
缓存和字段选择不是“锦上添花”,而是刚需
很多人开了 cache(true) 就以为万事大吉,结果缓存根本没复用;或者查 10 个字段只用 2 个,白白浪费 I/O 和带宽。
-
field()必须显式写,select()默认查所有字段;含 TEXT/JSON 字段的表,不加field()很可能拖慢接口 3 倍以上 -
cache(true)只写不读,等于白开;单条记录优先用findOrEmpty($id, ['cache' => ['tag' => 'user']]),它自动生成可复用的 key - 手动拼 cache key 时,大小写、下划线、前缀必须完全一致;常见错误是写
cache('user_'.$id)却读cache('user'. $id) - 软删除字段
delete_time、状态字段status、时间范围字段created_at这些高频 where 条件,必须进联合索引,且顺序要按等值→范围→排序排列
最常被忽略的一点:索引不是建了就完事。复合索引字段顺序错了,等于没建;字段类型和 PHP 传参类型不一致,等于白建;缓存 key 拼错了,等于白缓。优化不是堆技巧,而是每一步都得对得上 MySQL 的执行逻辑。



















