如何解决ThinkPHP索引错误及优化索引策略?
- 内容介绍
- 文章标签
- 相关推荐
本文共计983个文字,预计阅读时间需要4分钟。
索引增加了,查询反而变慢,或者加了一堆索引但查询仍然走全表扫描——这通常不是数据库的问题,而是索引配置不当的问题。ThinkPHP本身并不决定索引是否生效,它只负责生成SQL语句;真正起作用的是你创建的索引是否与查询条件匹配。
确保你的索引配置正确,与以下查询模式相匹配:
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() 虽然参数已替换,但依然不告诉你有没有走索引。
立即学习“PHP免费学习笔记(深入)”;
- 调试慢查询时,先用
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,比盲目加索引有用得多。
本文共计983个文字,预计阅读时间需要4分钟。
索引增加了,查询反而变慢,或者加了一堆索引但查询仍然走全表扫描——这通常不是数据库的问题,而是索引配置不当的问题。ThinkPHP本身并不决定索引是否生效,它只负责生成SQL语句;真正起作用的是你创建的索引是否与查询条件匹配。
确保你的索引配置正确,与以下查询模式相匹配:
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() 虽然参数已替换,但依然不告诉你有没有走索引。
立即学习“PHP免费学习笔记(深入)”;
- 调试慢查询时,先用
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,比盲目加索引有用得多。

