explain 显示 type 为 all 表示全表扫描,性能危险;需为查询字段建索引,注意复合索引顺序、避免函数操作、慎用 select * 和深分页,优先联合索引与游标分页。

Explain 显示 type 是 ALL,说明没走索引
这是最常见也最危险的信号:查询全表扫描,数据量一上万,响应就明显变慢。ThinkPHP 本身不干预 SQL 执行计划,问题出在你写的查询条件、字段顺序或索引设计上。
实操建议:
- 用
Db::name('user')->where('status', 1)->select()时,如果status字段没建索引,EXPLAIN的type就是ALL - 复合索引要注意字段顺序——
WHERE a = ? AND b = ?可用(a,b)索引,但WHERE b = ?单独用不了 - 避免在索引字段上做函数操作,比如
whereRaw('DATE(create_time) = ?', ['2024-01-01']),会直接让索引失效 - ThinkPHP 的
field()如果写了*或包含未索引字段,不影响索引选择,但会拖慢传输和内存消耗
ThinkPHP 的 order() 和 limit() 搞不定 Using filesort
Using filesort 不代表一定慢,但它意味着 MySQL 没法靠索引完成排序,得额外排序。尤其在分页场景下(比如 ->order('id desc')->limit(10)),如果 id 不是联合查询条件的一部分,就容易触发。
实操建议:
- 分页查最新记录时,别只依赖
order('create_time desc')—— 如果同时有where('status', 1),优先建联合索引(status, create_time) -
limit很小(如limit 10)但偏移大(如limit 10000,10),即使走了索引,性能也会断崖下跌;改用游标分页(WHERE create_time )更稳 - ThinkPHP 的
page()方法底层仍是limit offset,高偏移场景下要主动绕开
EXPLAIN 的 key_len 值比预期小,索引没用全
key_len 显示 MySQL 实际用了索引的前几个字节。比如你建了 (user_id, category_id, status) 联合索引,但 key_len 只显示前两个字段的长度,说明 status 条件没生效,或者用了 OR / != / IS NULL 这类中断索引连续性的写法。
实操建议:
- 检查 ThinkPHP 查询中是否混用了
where('status', '!=', 0)和where('status', 1)—— 前者大概率让后续字段索引失效 - 字符串字段建索引时指定了长度(如
VARCHAR(255)只索引前 191 字符),key_len对应的就是这个截断长度,不是 bug,是预期行为 - NULL 值处理要小心:
status允许为 NULL,又写了where('status', null),某些 MySQL 版本会放弃使用该字段索引
TP6 的 with() 关联查出 N+1,EXPLAIN 看不到但性能崩了
EXPLAIN 只分析单条 SQL,对 ThinkPHP 的关联加载(with('profile'))无能为力。它可能生成 1 条主查 + N 条子查,每条子查都 type=ALL,但你在 EXPLAIN 里只能看到第一条。
实操建议:
- 用
with(['profile' => function ($q) { $q->field('id, user_id, avatar'); }])显式限制子查询字段,避免SELECT *拖垮速度 - 确认关联字段是否有索引——
profile.user_id必须有索引,否则每条子查都是全表扫 - 高频列表页慎用嵌套
with(),优先考虑手动join或拆成两次明确查询,方便你单独EXPLAIN每一条 - 开启 ThinkPHP 的 SQL 日志(
'sql_explain' => true在数据库配置里),才能真实看到它到底发了几条 SQL
索引不是建了就完事,ThinkPHP 的链式调用看着简洁,但每一步都可能悄悄绕过你的索引设计。真正卡住性能的,往往不是 EXPLAIN 里最刺眼的那行,而是你没意识到它根本没执行到那里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











