必须用explain分析getlastsql()返回的真实sql:看type是否为all、key是否为null、key_len是否匹配联合索引前缀;左模糊、函数操作、范围查询后字段、排序字段未入索引等写法均导致失效。

ThinkPHP 里索引没生效,不是框架“不支持索引”,而是 SQL 写法或索引设计本身触发了 MySQL 的规避规则。直接看 buildSql() 没用,必须拿真实 SQL 去 EXPLAIN 验证。
怎么确认索引到底有没有被用上
ThinkPHP 的 buildSql() 只返回带问号占位符的 SQL 字符串,比如 SELECT * FROM user WHERE mobile = ?;它不执行、不分析、不告诉你 key 是不是 NULL。真正判断依据只有一个:EXPLAIN SELECT * FROM user WHERE mobile = '13800138000'(注意:用 getLastSql() 拿已替换参数的完整 SQL,或手动替换问号值)。
关键看 EXPLAIN 输出里的三个字段:
-
type:值为ALL就是全表扫描,索引失效;ref或range才算走索引 -
key:显示实际使用的索引名;NULL表示完全没用索引 -
key_len:长度对不上说明只用了联合索引的前缀,比如建了(user_id, status),但查询只用了status,key_len就会是 0
ThinkPHP 哪些链式写法会让索引当场失效
这些不是 ThinkPHP 的 bug,是底层 SQL 违反了 B+ 树索引的匹配逻辑:
-
where('name', 'like', '%abc')—— 左模糊,B+ 树没法从头定位,直接放弃索引 -
whereRaw('DATE(create_time) = "2024-01-01"')—— 对字段套函数,索引列值被“加工”后无法比对原始有序结构 -
where('category_id', 5)->where('price', '>', 99)->where('status', 1)—— 联合索引(category_id, price, status)中,price是范围查询,status后续字段就断掉了有序性,无法利用 -
where('user_id', $uid)->order('id desc')——user_id有索引但id没单独索引,排序仍要filesort,哪怕最终只取 10 条
给 ThinkPHP 表加索引,优先加哪几个字段
别一上来就给所有 where 字段各建一个单列索引——MySQL 优化器通常只选其中一个,其余变冗余。重点看高频组合场景:
- 经常一起出现的等值条件,比如
where('user_id', $uid)->where('status', 1),建联合索引(user_id, status),顺序不能反 - 带排序的查询,如
where('user_id', $uid)->order('created_at desc'),索引必须是(user_id, created_at),把排序字段放末尾 - 软删除字段
delete_time必须进索引:ThinkPHP 默认查数据会自动加上delete_time IS NULL,高频且筛选性强 - 避免给
TEXT、JSON字段建索引,哪怕只WHERE一次,也容易拖慢整个索引树效率
唯一索引冲突时,ThinkPHP 报错怎么接住
唯一索引重复插入触发的是数据库层错误(如 MySQL 错误码 1062),ThinkPHP 不会自动捕获并转成业务提示。硬写 try/catch 捕 PDOException 很脆弱,更稳的方式是:
- 先用
where查一遍是否存在:UserModel::where('mobile', $mobile)->value('id'),存在则提前返回 - 或者用
insertGetId()+ 数据库INSERT IGNORE/ON DUPLICATE KEY UPDATE语法(需在模型中通过query()手动拼) - 注意:事务内发生唯一冲突,会导致整个事务回滚失败,除非显式
rollback()并重新startTrans()
最常被忽略的一点:联合索引的字段顺序不是按“出现先后”,而是按“过滤强度 + 排序需求”定的;where 条件里漏掉最左字段,右边全作废——这点在 ThinkPHP 链式调用里特别隐蔽,因为代码看着是连着写的,但生成的 SQL 条件顺序和索引定义顺序未必对得上。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











