加索引不一定生效,需满足最左前缀、类型匹配、无函数包裹等条件;ThinkPHP查询是否走索引取决于生成SQL是否符合索引使用规则,须用EXPLAIN验证。

为什么 thinkphp 项目加了索引还是慢?
不是加了索引就一定生效,thinkphp 的查询构建器(如 where()、order()、limit())生成的 SQL 是否能命中索引,取决于字段顺序、类型匹配和查询写法。常见现象是:明明给 user_id 加了单列索引,但 Db::name('log')->where('user_id', 123)->where('status', 1)->select() 却没走索引——因为 MySQL 只能用上最左前缀,而 status 字段不在索引中,又没覆盖查询条件。
- 复合索引必须严格遵循「最左前缀原则」:
INDEX(user_id, status)能加速WHERE user_id = ?或WHERE user_id = ? AND status = ?,但对WHERE status = ?无效 -
LIKE查询中,LIKE 'abc%'可走索引,LIKE '%abc'或LIKE '%abc%'不行(除非用全文索引) -
ORDER BY字段若不在索引中,或顺序与索引不一致(如索引是(a,b),却写ORDER BY b,a),会导致 filesort -
thinkphp的field()若查了未被索引覆盖的字段(比如SELECT *),即使条件走了索引,仍需回表,性能打折扣
Db::table()->where() 中哪些写法会破坏索引?
ThinkPHP 封装让写法更简洁,但也容易无意中写出无法走索引的语句。核心问题是:它生成的 SQL 是否触发了隐式类型转换、函数包裹或 OR 条件拆分。
-
where('create_time', '>', '2024-01-01')是安全的;但where('DATE(create_time)', '2024-01-01')会让create_time字段被函数包裹,索引失效 -
where('mobile', '138****1234')如果数据库字段是VARCHAR,而传入的是整数(如13800001234),MySQL 可能触发隐式类型转换,导致索引失效 -
where('status', 'in', [0,1,2])一般可走索引;但若IN列表过大(如几百项),优化器可能放弃索引而走全表扫描 - 避免在
where中混用OR:如where('a', 1)->whereOr('b', 2),生成的 SQL 是WHERE a = 1 OR b = 2,通常无法有效利用单列索引,建议拆成 UNION 或补上复合索引
如何验证 ThinkPHP 查询是否真用了索引?
不能只看执行时间,得看 MySQL 底层是否命中索引。关键动作是开启慢查询日志 + 分析 EXPLAIN 结果,而不是依赖 ThinkPHP 的日志输出。
- 在数据库配置中打开慢查询:
slow_query_log = ON,long_query_time = 0.1(临时设为 0.1 秒抓更多线索) - 在 ThinkPHP 开发环境启用 SQL 日志:
'sql_explain' => true(5.x)或通过事件监听获取原始 SQL - 拿到 SQL 后手动执行
EXPLAIN FORMAT=TRADITIONAL SELECT ...,重点关注:type(应为ref/range/const,非ALL或index)、key(实际使用的索引名)、rows(扫描行数,越小越好)、Extra(避免出现Using filesort、Using temporary) - 注意:ThinkPHP 的
paginate()默认带count()查询,这个 COUNT 很容易因没索引或函数而变慢,建议用withCount()或缓存总数
哪些字段组合值得建复合索引?
别一上来就给所有 where 字段单独建索引。优先合并高频联合查询条件,控制数量(单表索引建议 ≤ 5 个),并按区分度从高到低排序。
立即学习“PHP免费学习笔记(深入)”;
- 登录日志表常查:
WHERE app_id = ? AND status = ? ORDER BY created_at DESC→ 建INDEX(app_id, status, created_at)(注意:created_at放最后,因是范围查询,放前面会截断索引使用) - 用户订单列表:
WHERE user_id = ? AND pay_status IN (1,2) AND is_deleted = 0→ 建INDEX(user_id, pay_status, is_deleted),其中user_id区分度最高,放最左 - 避免冗余索引:
INDEX(a)和INDEX(a,b)同时存在时,前者基本无用,可删 - 对
JSON字段(如 MySQL 5.7+ 的extra->>'$.source')无法直接建普通索引,需建生成列 + 索引:ALTER TABLE t ADD COLUMN source VARCHAR(32) AS (extra->>'$.source'), ADD INDEX idx_source (source);
索引不是越多越好,每个写操作都要维护索引。真正卡顿的点,往往藏在 count()、GROUP BY、JOIN 关联字段缺失索引,或者分页偏移量极大(LIMIT 100000, 20)却没用游标分页。这些地方光加索引没用,得换思路。



















