type=all 表示全表扫描,是最需警惕的慢查询标志;常见原因包括where字段无索引、使用函数、隐式类型转换、left join右表无索引等,需通过explain验证索引是否生效。

Explain 查看 SQL 执行计划时,type=ALL 是什么信号
这代表全表扫描,是最需警惕的慢查询标志。ThinkPHP 默认不自动显示执行计划,必须手动在 Db::query() 或 ->fetchSql(true) 获取原始 SQL 后,用 MySQL 客户端或 phpMyAdmin 执行 EXPLAIN SELECT ...。
常见诱因包括:WHERE 条件字段没索引、用了函数如 DATE(create_time)、隐式类型转换(如字符串字段查整数)、LEFT JOIN 的右表无关联索引。
- 用
->fetchSql(true)拿到语句后,务必在真实数据量环境下 Explain,本地小表可能掩盖问题 - 注意
key列是否为NULL—— 表示没走索引;rows值接近表总行数,基本等于全扫 - ThinkPHP 6 的
->debug(true)只输出 SQL 和耗时,不包含 Explain 结果,别混淆
给 WHERE 字段加索引前,先确认字段选择性
不是所有 WHERE 字段都适合建索引。比如 status 只有 0/1 两个值,单独建索引效果极差,还拖慢写入。真正有效的是高区分度字段(如 user_id、order_no)或组合条件。
ThinkPHP 中常见低效写法:where('status', 1)->where('is_deleted', 0),若两者都低区分度,单列索引几乎无效;应改为联合索引 (status, is_deleted),且顺序按查询频率和区分度调整(高频+高区分度字段放前面)。
- 用
SELECT COUNT(DISTINCT field) / COUNT(*) FROM table粗略估算选择性,> 0.1 才值得单列索引 - 联合索引要覆盖查询中所有 WHERE 条件字段,但不必包含 SELECT 的字段(除非用到覆盖索引优化)
- ThinkPHP 迁移文件里建索引用
$table->index(['status', 'is_deleted'])->execute();,注意字段顺序不可颠倒
LIKE '%关键词%' 为什么无法走索引,怎么改
以通配符开头的模糊查询(LIKE '%abc' 或 LIKE '%abc%')会导致索引失效,因为 B+ 树无法从中间或结尾匹配。ThinkPHP 的 where('title', 'like', '%php%') 就是典型陷阱。
可行解法分场景:
- 搜索需求固定前缀(如标题开头含“教程”)→ 改用
LIKE '教程%',可走索引 - 必须全文模糊 → 舍弃 LIKE,改用 MySQL 全文索引(
FULLTEXT)或 ElasticSearch;ThinkPHP 需手动执行Db::query("SELECT * FROM article WHERE MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE)", ['php']) - 数据量不大(FORCE INDEX 强制走某个索引(不推荐,只是临时止血)
ThinkPHP 关联查询慢,不只是 N+1 问题
with(['user', 'category']) 看似优雅,但若 user 关联表没在 user_id 上建索引,或 category 表缺少 id 主键,就会触发大量随机 I/O。Explain 会显示 type=ref 但 rows 极高,甚至变 ALL。
更隐蔽的问题是关联字段类型不一致:ThinkPHP 模型定义 protected $pk = 'id';,但数据库里关联字段是 user_id BIGINT UNSIGNED,而主表 id 是 INT,MySQL 会做隐式转换,导致索引失效。
- 检查所有关联字段的类型、符号(SIGNED/UNSIGNED)、长度是否完全一致
- 关联查询前,确保外键字段(如
order.user_id)有索引,且与主表主键类型严格匹配 - 用
->with(['user' => function ($query) { $query->field(['id', 'name']); }])显式指定字段,避免SELECT *拖慢网络和内存
实际优化中最容易被跳过的,是验证索引是否真的被用上 —— 很多人建完索引就以为万事大吉,但 Explain 里的 key 列没变,说明优化根本没生效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











