查数据库慢首要检查索引缺失:user_id、status、created_at等where字段需手动建索引;复合条件按顺序建联合索引;like避免前导通配;关联查询慎用with,优先join或懒加载;分页禁用自动count,改用近似值或覆盖索引;批量操作用insertall/update而非循环单查;及时释放内存,用chunk或toarray。

查数据库慢,先看是不是没加索引
ThinkPHP 本身不自动建索引,where 条件里用了 user_id、status、created_at 却没在数据库加索引,查询就会全表扫——哪怕只查 10 行,也可能耗时 800ms。
实操建议:
- 用
EXPLAIN看 SQL 执行计划,重点盯type是不是ALL(全表扫描) - 复合查询条件如
where(['status' => 1, 'is_deleted' => 0]),建联合索引要按字段顺序来,比如(status, is_deleted),反过来效果差很多 -
LIKE '%关键词'这种前导通配没法走索引,能改成LIKE '关键词%'就改;不能改就考虑fulltext或 ES
模型关联查太多,容易 N+1 和内存爆掉
写 with('profile', 'orders') 看似方便,但 100 条用户数据会触发 200+ 次额外查询,还可能把整张订单表拖进内存。
实操建议:
- 用
with(['profile' => function ($q) { $q->field('id, user_id, avatar'); }])显式指定字段,避免SELECT * - 关联表数据量大时,别用
with,改用load()懒加载 + 批量预取,或直接手写join - 确认是否真需要关联数据:列表页通常只要头像和昵称,详情页才查完整 profile,别一上来全 with
count() 和分页在大数据量下卡死
ThinkPHP 分页默认先 COUNT(*) 再 LIMIT,当表有 500 万行,COUNT(*) 可能跑 3 秒以上,用户还没看到第一页就超时了。
实操建议:
- 用
paginate(15, false, ['query' => request()->param()])关闭自动 count,自己用近似值或缓存总数(比如 Redis 记录最新统计) - 真要精确总数且数据量大,把
COUNT改成覆盖索引查询,例如SELECT COUNT(user_id) FROM user WHERE status = 1(前提是user_id非空且有索引) - 超过 10 万条就别让用户翻到最后几页,前端限制最大页码,后端加
if ($page > 2000) { throw new HttpException(400); }
Db::query() 和 Db::execute() 乱用导致连接池打满
在循环里反复调用 Db::query() 查单条记录,或者用 Db::execute() 执行没参数绑定的 SQL,不仅慢,还会让 PDO 连接反复创建销毁,高并发时直接连不上库。
实操建议:
- 批量操作一律用
Db::insertAll()、Db::update()带where in条件,而不是 foreach + 单条 update - 原生 SQL 必须用参数绑定:
Db::query("SELECT * FROM user WHERE id = ?", [$id]),别拼字符串 - 确认是否必须用 Db 类:简单读写优先走
pdo->prepare()+ 手动 bindParam,更可控;复杂业务逻辑才用模型封装
最常被忽略的是「查完不用的数据还在内存里」——比如用 select() 拿了 2000 条记录,只渲染前 20 条,剩下 1980 条对象一直占着内存,GC 不及时就拖慢整个请求。该用 chunk() 就 chunk,该 toArray() 就转数组,别留着完整的模型实例。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










