ThinkPHP查询慢八成因未走索引、字段冗余或缓存失效;应通过getLastSql()+EXPLAIN验证索引使用,避免函数操作和左模糊,建联合索引遵循最左匹配,并启用字段缓存与配置优化。

ThinkPHP 查询慢,八成不是框架拖后腿,而是 SQL 没走索引、字段选多了、缓存没开对——直接改这几点,响应时间常能从 800ms 掉到 80ms。
怎么确认查询到底有没有走索引
别信 buildSql() 输出的 SQL 看着“干净”就以为没问题。它只拼字符串,不执行,更不告诉你用没用上索引。
-
buildSql()返回的是带问号占位符的语句(如WHERE name = ?),得先用getLastSql()拿到真实值替换后的 SQL - 把
getLastSql()结果复制进 MySQL 客户端,前面加EXPLAIN执行,重点看type(非ALL才算走了索引)、key(非NULL)、rows(越小越好) - 常见假象:
WHERE status = 1 AND score > 80,如果建的是(status)单列索引,score那段就失效;必须建(status, score)联合索引才有效
哪些 ThinkPHP 写法会悄悄让索引失效
ORM 链式调用掩盖了底层 SQL 的危险操作,一写错,索引直接作废。
-
where('name', 'like', '%abc')—— 左模糊,B+ 树没法前缀匹配,key必为NULL -
whereRaw('DATE(create_time) = "2024-01-01"')—— 对字段套函数,索引失效;应改写为whereBetween('create_time', ['2024-01-01 00:00:00', '2024-01-01 23:59:59']) -
where('user_id', $uid)->order('id desc')->limit(10)—— 若id无索引,Using filesort会出现在Extra字段里,排序变全表扫描 -
where('category_id', 5)->where('status', 1)->where('score', '>', 70)—— 复合索引必须按顺序覆盖:(category_id, status, score)可用;(category_id, score)则status后面的score就断了
字段和索引怎么配才不浪费资源
不是“所有 where 字段都加索引”就安全,冗余索引反而拖慢写入,还可能干扰优化器选错执行计划。
立即学习“PHP免费学习笔记(深入)”;
- 高频等值 + 排序组合场景,优先建联合索引:比如常查
where('user_id', $uid)->order('created_at desc'),索引就建(user_id, created_at),别拆成两个单列 - 避免重复:已有
(a, b),就别再单独建(a);MySQL 5.7+ 会自动用前缀,(a)纯属冗余 - TEXT / JSON 字段禁止建索引,哪怕只
WHERE json_extract(xxx)一次,也容易触发索引膨胀甚至全表扫描 - 主键
id不用管,但检查迁移脚本是否误删了它——没主键的表,InnoDB 会自建隐藏聚簇索引,性能更差
缓存和配置动哪里见效最快
上线前不调这几个开关,等于开着空调跑马拉松——白耗资源。
- 关掉调试模式:
'app_debug' => false,否则日志刷爆磁盘、模板不编译缓存、SQL 日志全开 - 开字段缓存:
'fields_cache' => true,避免每次查表都去INFORMATION_SCHEMA查元数据 - 用
cache()包住稳定数据:比如用户中心页的菜单配置,Db::name('menu')->cache('menu_main', 3600)->select(),比每次都查快一个数量级 - 生成配置/路由缓存:
php think optimize:config和php think optimize:route,跳过运行时解析,尤其适合路由规则多的后台系统
最易被忽略的一点:EXPLAIN 看的是单条 SQL,但 ThinkPHP 的 N+1 查询(比如循环里反复 find())会让几十次 type=const 的快查询,累出整体秒开。先用 getLastSql() 抓出所有实际执行语句,再批量 EXPLAIN,才能真正看清瓶颈在哪。



















