explain显示type=all说明mysql正在全表扫描,需手动获取真实sql执行explain,重点检查type、key、rows三列,并在大数据量环境下测试索引有效性。

ThinkPHP6 查询慢,八成是索引没建对,不是代码写得差——where 里用了 user_id、status、created_at 却没在数据库加索引,EXPLAIN 一跑就是 type=ALL,哪怕只查 10 行也卡 800ms。
EXPLAIN 显示 type=ALL 怎么快速定位原因
这是最直接的信号:MySQL 正在全表扫描。ThinkPHP 不会自动帮你分析执行计划,必须手动介入。
- 先用
$query->fetchSql(true)或Db::getLastSql()拿到真实 SQL(注意:buildSql()返回的是带问号占位符的语句,不能直接 EXPLAIN) - 把 SQL 粘到 MySQL 客户端或 phpMyAdmin 里,前面加
EXPLAIN执行 - 重点看三列:
type(ALL 就是全扫)、key(为 NULL 表示完全没走索引)、rows(接近总行数基本等于白扫) - 别在本地小数据表上测——索引失效问题在百万级数据下才暴露,测试环境得尽量贴近线上量级
WHERE 条件字段怎么建索引才有效
不是所有字段都值得单独建索引。低区分度字段(如只有 0/1 的 status)单独建索引几乎无效,还拖慢写入。
- 优先建联合索引,顺序按查询高频 + 高区分度排列:比如常查
where(['status' => 1, 'is_deleted' => 0, 'user_id' => 123]),就建(status, is_deleted, user_id),而不是反过来 -
LIKE '%关键词'这种前导通配,B+ 树没法匹配,索引必然失效;能改成LIKE '关键词%'就改,不能就别指望索引,考虑FULLTEXT或 ES - 避免在索引字段上做函数操作:
whereRaw('DATE(create_time) = ?', ['2024-01-01'])会让整个字段索引失效;应改用范围查询:whereBetweenTime('create_time', '2024-01-01', '2024-01-02') - 字符串字段建索引时若指定了长度(如
VARCHAR(255)只索引前 191 字符),key_len显示的就是这个截断长度,不是 bug
with() 关联查询为什么越查越慢
EXPLAIN 看不到问题,但内存和耗时会崩——with('profile', 'orders') 在查 100 条用户时,可能触发 200+ 次额外查询,整张订单表数据被拖进 PHP 内存。
- 列表页通常只要头像、昵称等少量字段,别用
with查完整模型;显式限定字段:with(['profile' => function ($q) { $q->field('id, user_id, avatar'); }]) - 关联数据量大或非必现时,改用
load()懒加载 + 批量预取,或者直接手写join,由数据库一次拼好 - 确认业务是否真需要关联数据:详情页再查完整 profile,列表页用
field()控制输出字段即可 -
with默认是SELECT *,字段越多,网络传输和 PHP 解析开销越大,尤其 JSON 类型字段容易撑爆内存
分页 COUNT(*) 卡死怎么办
ThinkPHP 分页默认先执行 COUNT(*) 再 LIMIT,500 万行表上这一 COUNT 可能跑 3 秒以上,用户还没看到第一页就超时了。
- 禁用自动 count:
paginate(15, false, ['query' => request()->param()]),自己提供总数(比如从缓存读近似值,或用覆盖索引快速估算) - 高偏移分页(如
limit 10000, 10)即使走了索引也极慢,改用游标分页:where('create_time order('create_time desc') - 批量操作别用循环:
insertAll()/update()替代单条save(),减少数据库往返次数 - 大结果集别用
select()全捞进内存,改用chunk(500, function ($list) { ... })或toArray()控制结构深度
真正卡住的点往往不在框架调用本身,而在你没意识到的索引顺序、隐式类型转换、LEFT JOIN 右表缺索引这些细节。一个 EXPLAIN 能解决 70% 的慢查,但得你亲手去跑,而不是靠 debug(true) 看耗时就下结论。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











