thinkphp模型查询性能优化需四步协同:显式指定field()查字段、缓存key严格对齐、关联查询收敛字段、软删除等关键字段必须加索引。

ThinkPHP 的模型查询(不是 Eloquent,那是 Laravel 的)默认查全字段、不带索引提示、缓存 key 不对齐,接口一压就慢。别怪框架,是没按数据库基本功来用。
field() 必须显式写,不能靠“反正只用 name”侥幸
ThinkPHP 的 find()、select() 默认走 SELECT *,哪怕你只取 $user->name 和 $user->avatar。TEXT、JSON 字段一拖,磁盘读、网络传、PHP 解析全浪费。
-
UserModel::where('id', 1)->field('id,name,avatar,updated_at')->find()✅ - 避免
field('')或field('*'):TP 不校验字段,但 MySQL 会直接报错 - 字段含关键字(如
order、group)必须用反引号:field('`order`,status') - 关联预载入时,每个
with()子模型也得单独加field(),否则主表 + 关联表字段叠加更危险
with() 预加载字段限制必须写在闭包里
字段限制写在模型关联定义里、或链在 with('profile')->field() 后面,都不生效。只有闭包方式能真正控制 SQL 的 SELECT 字段。
- 正确:
UserModel::with(['profile' => function ($q) { $q->field('id,user_id,nickname,avatar'); }])->select() - 错误:
UserModel::with('profile')->field('nickname')→ 只影响主表,profile 还是查全字段 - 外键(如
user_id)和关联主键(如id)必须包含在field()中,否则$user->profile是空数组 - 多个关联要分别写闭包:
with(['profile' => fn($q) => $q->field('...'), 'posts' => fn($q) => $q->field('...')])
cache(true) 不等于缓存生效,key 对不上就白搭
cache(true) 只是把结果塞进缓存,下次查询不会自动命中——除非你手动拼完全一致的 key 再去读,或者用封装好的方法。
- 单条记录优先用:
UserModel::findOrEmpty($id, ['cache' => ['tag' => 'user']]),它自动生成并复用think_model_user_123这类 key - 手动管理 key 时,大小写、下划线、前缀必须一字不差;常见错误是写
cache('user_'.$id)却读cache('user'. $id) - 缓存 tag(如
'tag' => 'user')比纯 key 更实用:清缓存时可批量执行Cache::tag('user')->clear() - 生产环境慎用
file驱动;高并发下文件锁会让缓存变慢,建议切redis
paginate() 卡死?游标分页比 offset 更靠谱
当数据量超 10 万行,paginate(20) 查第 500 页(OFFSET 9980)时,MySQL 仍要扫描前 9980 行才能取 20 条——这不是 TP 的问题,是 SQL 本身缺陷。
- 改用游标分页:
Db::name('user')->where('id', 'limit(20)->select(),性能几乎恒定 - 游标字段(如
id或created_at)必须有索引,且值唯一、单调递增/递减 - 关闭自动 count:
paginate(15, false, ['query' => request()->param()]),自己用覆盖索引统计(如SELECT COUNT(user_id) FROM user WHERE status = 1) - 前端限制最大页码,后端加兜底:
if ($page > 2000) { throw new HttpException(400); }
最常被忽略的是:字段限制后关联数据为空,往往不是代码写错了,而是漏了外键或主键;缓存开了却没命中,大概率是 key 拼写不一致或驱动选错。这些点不盯住,优化就停在表面。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











