
分页查询超时,八成不是数据量大,而是索引根本没走——paginate() 一执行就卡住,EXPLAIN 显示 type=ALL、key=NULL,说明 MySQL 正在全表扫描。
where 条件写错,联合索引直接作废
ThinkPHP 的链式 where() 看着简洁,但字段顺序一错,联合索引就形同虚设。比如你建了联合索引 (status, category_id, created_at),但查询写成:
-
where('category_id', 5)->where('status', 1)→ 缺少最左列status,索引完全不触发 -
where('status', ['>', 0])->where('category_id', 5)→status是范围查询,category_id无法继续用索引顺序 -
where('status', 1)->where('created_at', '>=', '2024-01-01')→created_at在等值条件后,但前面没覆盖category_id,索引只用到status列
正确做法是严格按索引定义顺序写条件,并避免在索引字段上做范围操作后还依赖后续字段排序或过滤。
paginate() 默认 COUNT(*) 成性能黑洞
paginate() 内部会先执行一次 SELECT COUNT(*),如果带了复杂 JOIN、子查询或未优化的 WHERE,这个 COUNT 本身就会慢到超时。
立即学习“PHP免费学习笔记(深入)”;
- 现象:
Db::name('order')->join('user', 'order.user_id=user.id')->where('user.status', 1)->paginate(15)卡住,但去掉paginate()直接查数据很快 - 原因:COUNT(*) 无法利用覆盖索引,又没法下推到 JOIN 条件里,MySQL 得临时合并再统计
- 替代方案:改用游标分页,例如
where('id', '>', $lastId)->limit(16),彻底绕过 COUNT;或手动缓存总数(适合总数变动不频繁的场景)
POST 查询 + 分页 = 参数丢失 + 索引失效连环套
用 POST 提交大量查询条件时,ThinkPHP 默认分页生成的是 GET 链接,导致点击“下一页”时条件全丢——后端收不到 where 参数,paginate() 实际查的是无条件全表,自然触发索引失效和超时。
- 典型错误:表单
method="POST",但分页 HTML 是<a href="/list?page=2">2</a>,服务端收不到 POST 数据,where条件为空 - JS 补救有坑:用脚本把分页链接转成表单提交,但没重置
p(当前页码)参数,导致换条件后仍从第 2 页起查,结果漏数据 - 更稳做法:前端把所有查询条件序列化进 URL 的 query string(哪怕长一点),或改用 AJAX 分页,让每次请求都携带完整条件,避免服务端逻辑分支
隐式类型转换让索引“看不见”
数据库字段是 VARCHAR,PHP 传整数进去,MySQL 会悄悄做 CAST,索引立刻失效——这种问题在分页场景特别隐蔽,因为 getLastSql() 看不出类型,只有 EXPLAIN 能揭穿。
- 例子:
where('mobile', 13800138000)→ 若mobile是字符串类型,MySQL 实际执行CAST(mobile AS SIGNED) = 13800138000,key=NULL - 验证方式:把
Db::getLastSql()拿到的 SQL 粘进 MySQL 客户端,加EXPLAIN前缀执行,盯紧type和key字段 - 修复动作:统一用字符串传参,如
where('mobile', '13800138000');建表时对高频查询字段明确类型,别混用
真正卡住分页的,往往不是 limit 多大,而是前面那条 COUNT 或 where 条件有没有走索引——EXPLAIN 不看,光调 paginate() 参数,等于蒙眼修车。



















