数据库性能瓶颈多源于查询环节,如n+1、全表扫描、重复查询、字段冗余及缓存失效;需用debugbar定位n+1、with预加载避免懒加载、建复合索引匹配where条件、合理使用remember()并主动管理缓存。

数据库性能瓶颈几乎都出在查询环节,而不是 PHP 本身。Laravel 应用响应慢、列表卡顿、CPU 占用高,十有八九是没管住 Eloquent 的查询行为——N+1、全表扫描、重复查、字段拖累、缓存空转。
怎么快速发现 N+1 查询
别靠猜。开发环境装 barryvdh/laravel-debugbar,它会在 Queries 面板里把 N+1 查询标红,一眼就能看到几十条相似的 select * from users where id = ?;或者手动开日志:
DB::enableQueryLog();
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // 这里每循环一次就触发一次查 user
}
dd(DB::getQueryLog());
真正有效的定位方式是模拟真实请求路径,比如用 php artisan tinker 跑一遍控制器逻辑,再看日志——否则只查模型不走中间件和关联链,容易漏掉隐藏的懒加载。
with() 预加载不是加了就完事
with() 只解决“查多少次”,不解决“查什么”和“查多深”。常见踩坑点:
- 写
Post::with('user')->get(),但模板里用了$post->user->avatar和$post->user->profile->bio,结果还是触发懒加载——必须显式写成Post::with('user.profile') - 用
withCount('comments')替代循环里调$post->comments->count(),否则每个count()都是一次SELECT COUNT(*) - 关联表字段太多时,
with('user')默认查users.*,实际只要id和name,就得限制:Post::with(['user' => function ($q) { $q->select('id', 'name'); }])
索引不是“有就行”,得看 WHERE 和 JOIN 怎么写
加了 $table->index('user_id') 不代表万事大吉。关键看 SQL 执行计划是否真用上:
- 复合查询条件如
where status = ? and created_at > ?,单列索引效果有限,要建复合索引:$table->index(['status', 'created_at']) - 外键字段(如
user_id)必须加索引,否则join或where会全表扫描 - 避免在
where里对字段做函数操作,比如whereDate('created_at', '2025-01-01')或whereRaw('DATE(created_at) = ?'),这会让索引失效 - 用
EXPLAIN看执行计划:在 MySQL 客户端执行EXPLAIN SELECT * FROM posts WHERE user_id = 123,确认type是ref或range,不是ALL
缓存 remember() 很好用,但过期时间和键名得想清楚
remember() 不是万能胶布,乱贴反而让数据不一致或内存爆掉:
- 缓存键不能太宽泛,比如
User::remember(3600)->get()缓的是所有用户,哪怕只改了一个用户也得等一小时才更新——应该按业务场景拆,例如Cache::remember('active_users', 3600, fn() => User::where('active', 1)->get()) - 缓存时间不是越长越好。用户列表可能 5 分钟就够了,热门文章可以 1 小时,但登录态、权限这类敏感数据根本不该缓
- 不要在事务里用
remember(),缓存写入和数据库提交不同步,容易读到脏数据 - 记得清缓存:当数据被修改时,主动
Cache::forget('active_users'),而不是干等过期
最常被忽略的是缓存穿透和缓存雪崩——没数据时反复查 DB,或大量缓存同时过期导致 DB 瞬间被打垮。简单方案是加空值缓存 + 随机过期时间偏移,但别在 remember() 里硬塞逻辑,该用底层驱动控制就用底层驱动。











