查慢主因是字段冗余、N+1查询、索引失效和外键配置错误;应显式select字段、用with预加载、建匹配联合索引、规范外键定义,并通过EXPLAIN和查询日志定位问题。

查太多字段就慢,select() 必须写清楚
默认用 Model::all() 或 get() 会查出所有字段,哪怕你只显示标题和时间。数据库多传几十KB没用数据,网络+ORM解析都白耗资源。
实操建议:
- 永远显式调用
select('id', 'title', 'created_at'),不依赖fillable或模型定义 - 关联查询里也得约束:比如
with(['author' => function ($q) { $q->select('id', 'name'); }]) - 避免
select('*')—— 它和不写select()一样危险,且掩盖了字段膨胀问题 - 如果要用
toArray()后处理,先确认前端真需要那些字段;否则加appends或访问器反而更拖慢
N+1 查询不是报错,是性能雪崩的静音警报
写 $posts = Post::get(); foreach ($posts as $post) { echo $post->user->name; } 看似正常,实际发了 1 + N 条 SQL。100 篇文章就是 101 次查询,数据库连接池可能直接打满。
实操建议:
- 用
with()预加载,但别无脑with('user', 'category', 'tags')—— 查三张表关联,可能触发笛卡尔积 - 深度嵌套时拆开查:比如先取
$postIds = $posts->pluck('id'),再用User::whereIn('id', $postIds)->get()->keyBy('id')手动映射 - 用
DB::enableQueryLog()+dd(DB::getQueryLog())在本地验证是否真解决了 N+1(注意仅开发环境) - Laravel Telescope 或 Laravel Debugbar 能直观看到查询次数,比猜靠谱得多
where() 条件顺序和索引失效,比代码逻辑还致命
写 where('status', 1)->where('created_at', '>', now()->subDays(7)) 很自然,但如果表没给 (status, created_at) 建联合索引,MySQL 可能只用上 status,后面全靠扫描。
实操建议:
- 用
EXPLAIN看执行计划:在 Tinker 或数据库客户端里跑EXPLAIN SELECT * FROM posts WHERE status = 1 AND created_at > '2024-06-01' - 索引字段顺序要匹配最常用查询条件的「选择性」——高区分度字段(如
user_id)放前面,低区分度(如status)放后面 - 避免在
where里对字段做函数操作:whereDate('created_at', '2024-06-01')会让索引失效,改用whereBetween('created_at', [$start, $end]) - 大表分页慎用
skip/take,偏移量越大越慢;改用游标分页(whereIdGreaterThan($lastId))或cursorPaginate()
关系模型里 belongsTo 和 hasMany 的外键没设对,优化全白搭
明明写了 belongsTo(User::class),却没在迁移里加 $table->foreignId('user_id')->constrained(),或者模型里漏了 foreignKey 参数,Laravel 就会默默走全表扫描找匹配项。
实操建议:
- 检查迁移文件:外键字段名必须和模型里约定一致(默认是
{relation}_id),否则手动指定foreignKey和ownerKey - 用
php artisan schema:dump生成当前结构快照,对比线上库是否少了索引 - 不要依赖 ORM 自动推断外键——它只按命名猜,一旦命名不规范(比如叫
author_id却关联User),就得显式写belongsTo(User::class, 'author_id') - 复合外键场景极少,但一旦出现(比如多租户),必须用
wherePivot()或原生查询,别硬套 Eloquent 关系
真正卡顿的点,往往不在 SQL 写得多炫,而在字段、索引、外键、预加载这四块有没有对齐。查慢了先看日志里那几条 SQL 是不是重复、带不带 Using filesort 或 Using temporary,而不是急着加缓存。


















