ThinkPHP中索引失效主因是SQL写法不当:左模糊('%abc')、字段函数(DATE(create_time))、排序字段无索引、!=/NULL中断最左前缀;联合索引须按等值→范围→排序顺序设计,验证需用getLastSql()+EXPLAIN。

索引不是加了就生效,ThinkPHP 查询慢的主因往往不是没建索引,而是索引建了但 SQL 写法让 MySQL 直接弃用——你得先让查询“能走索引”,再谈“走得好不好”。
ThinkPHP 的 where() 哪些写法会让索引失效
框架链式调用看着干净,但一写错,EXPLAIN 就显示 type: ALL、key: NULL,等于白建索引。
-
where('name', 'like', '%abc')—— 左模糊,B+ 树没法从头匹配;应改成where('name', 'like', 'abc%')或换用全文索引 -
whereRaw('DATE(create_time) = "2024-01-01"')—— 对字段做函数操作,索引失效;改用whereBetween('create_time', [$start, $end]) -
where('user_id', $uid)->order('id desc')—— 若只有user_id索引,id没索引,排序会触发Using filesort;要提速就得建(user_id, id)联合索引 -
where('status', '!=', 0)或where('status', null)—— 这类条件容易中断最左前缀匹配,导致联合索引后半段失效
联合索引字段顺序怎么排才对
MySQL 遵循最左前缀原则,顺序错了,索引就形同虚设。别凭感觉排,按实际查询模式来。
- 高频等值 + 范围组合:比如常查
where('category_id', 5)->where('status', 1)->where('create_time', '>=', $t),索引应为(category_id, status, create_time);create_time放最后,它才能参与范围筛选 - 带排序的查询:如
where('user_id', $uid)->order('created_at desc'),索引必须是(user_id, created_at);若写成(created_at, user_id),user_id等值条件就无法利用索引有序性 - 已有
(a, b),就别再单独建(a);MySQL 5.7+ 会自动跳过单列索引,属于冗余
怎么验证 ThinkPHP 查询到底有没有走索引
buildSql() 只输出 SQL 字符串,不告诉你执行计划;getLastSql() 才是关键——它返回参数已替换的真实 SQL,可直接丢进 MySQL 执行 EXPLAIN。
立即学习“PHP免费学习笔记(深入)”;
- 在
config/database.php加'sql_explain' => true,TP 会自动对每个SELECT打印EXPLAIN结果 - 手动验证时,用
$query->getLastSql()拿完整 SQL,复制到 MySQL 客户端执行EXPLAIN SELECT ... - 重点看三列:
type(ref/range合理,ALL危险)、key(是否命中你建的索引名)、rows(扫描行数是否接近实际命中的数据量) -
key_len值比预期小,说明索引没用全——可能是用了OR、!=、IS NULL,或字符串字段只索引了前缀长度(如VARCHAR(255)只建了 191 字节索引)
游标分页为什么比 page() 更稳
大数据量下用 paginate(20) 查第 500 页,MySQL 仍得扫 9980 行;换成游标分页(比如 where('id', '>', $lastId)->limit(20)),性能才稳定——但前提是 id 或 created_at 必须有索引,且排序方向与索引顺序一致。
-
page()底层是LIMIT offset, size,偏移越大,MySQL 越要跳过前面大量数据,即使走了索引也慢 - 游标分页依赖「上一页最后一条记录的排序字段值」,只要该字段在联合索引末尾(如
(status, id)),就能跳过扫描,直接定位 - 注意:不能混用
ORDER BY id DESC和WHERE id > ?,方向必须一致;否则索引无法覆盖 - 软删除场景下,
whereNull('delete_time')可走索引,但记得把delete_time包进联合索引(如(status, delete_time))
最容易被忽略的是:索引是否真正覆盖了你的查询模式。很多开发者建了 (a, b, c),却在代码里写 where('b', 1)->where('c', 2),结果 EXPLAIN 显示 key: NULL —— 因为没满足最左前缀,连第一个字段 a 都没用上。



















