加了索引仍慢或全表扫描,主因是索引未匹配查询模式:联合索引未按等值优先、范围在后顺序创建;字段上使用函数、隐式转换、前导模糊匹配(%xxx)导致失效;单列冗余索引浪费资源;未用EXPLAIN验证实际执行计划。

索引加了反而更慢,或者加了一堆但查询还是走全表扫描——这不是数据库的问题,是索引没按查询模式建。ThinkPHP 本身不决定索引是否生效,它只负责生成 SQL;真正起作用的是你建的索引是否匹配 WHERE、ORDER BY 和字段顺序。
WHERE 条件字段没按等值+范围顺序建联合索引
复合查询里字段顺序错了,索引就等于白建。比如经常写:where('status', 1)->where('create_time', '>=', $time),却建了 INDEX idx_created_status (create_time, status),那 status 根本用不上。
- 必须把等值条件放前面,范围条件放后面:
INDEX idx_status_created (status, create_time) - 多个等值条件(如
user_id+category_id)顺序无所谓,但要和代码中where()的调用顺序尽量一致(避免优化器误判) - 别给单个字段反复建单列索引,比如已有
(user_id, status),再单独建INDEX idx_user_id (user_id)就是冗余
对索引字段用了函数或表达式
whereRaw('DATE(create_time) = "2024-01-01"') 或 where('mobile', 'like', '%138') 这类写法,MySQL 直接放弃走索引,explain 里 type 是 ALL,key 是 NULL。
- 时间范围查,用
whereBetween('create_time', [$start, $end]),别套DATE()、YEAR() - 模糊搜索要左匹配?改用全文索引或 Elasticsearch,别硬扛;能右匹配(
'abc%')就优先用 - 软删除字段
delete_time常用于whereNull('delete_time'),这个可以走索引,但记得建索引时包含该字段(如INDEX idx_active (status, delete_time))
EXPLAIN 不看,光靠 buildSql() 猜索引有没有生效
buildSql() 只输出带问号的 SQL 字符串,看不出执行计划;getLastSql() 虽然参数已替换,但依然不告诉你有没有走索引。
- 调试慢查询时,先用
Db::getLastSql()拿到完整 SQL - 粘贴进 MySQL 客户端,手动加
EXPLAIN前缀执行,重点看type(ref/range合理,ALL就是全表扫描)、key(实际用的索引名)、rows(预估扫描行数) - ThinkPHP 配置里开
'sql_explain' => true,它会自动对每个SELECT打印 explain 结果,比手动更省事
游标分页字段没索引,或索引没覆盖排序+查询
大数据量下用 paginate(20) 查第 500 页,MySQL 仍得扫 9980 行;换成游标分页(比如 where('id', 'limit(20)),性能才稳定——但前提是 id 或 created_at 必须有索引,且排序方向与索引顺序一致。
- 倒序分页(
order('created_at desc'))对应索引必须是(created_at DESC),不是默认升序 - 如果用
where('status', 1)->order('created_at desc'),索引就得是(status, created_at DESC),否则排序仍触发filesort - 别指望
id主键索引能“顺便”加速where status=1 order by created_at—— 覆盖不到就是覆盖不到
最常被忽略的一点:索引不是建完就一劳永逸。业务逻辑变、查询条件变、数据分布变,原来高效的索引可能变成负担。定期用 EXPLAIN 回检关键接口的 SQL,比盲目加索引有用得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











