根本原因是数据库查询未优化,需改查询逻辑:预加载关联避免n+1、精简select字段、加索引(含外键和复合索引)、用explain验证、游标分页替代offset。

直接原因是数据库查询没优化,不是 Laravel 慢,是写法让数据库反复跑、读得多、算得慢。重点不是换框架,而是改查询逻辑和数据加载方式。
预加载关联关系,砍掉 N+1 查询
这是最常见也最有效的切入点。比如列表页要显示每条记录的作者名、分类名、状态标签,如果在模板里写 $post->author->name 或 $post->category->title,Laravel 默认会为每条记录单独查一次关联表——100 条数据就触发 100 次额外查询。
- 控制器中用
with()一次性加载所需关联:Post::with('author', 'category', 'tags')->get() - 深层嵌套别硬套,比如
author.profile.avatar,优先考虑是否真需要,或拆成两层加载 - 确认模型里已正确定义关系方法(如
belongsTo、belongsToMany),否则with不生效
精简字段,别 SELECT *
拉取整行数据再丢掉 80% 字段,既浪费数据库 I/O,又加重 PHP 序列化和网络传输负担。尤其含大文本(content、description)、JSON 字段或敏感字段(password_hash)时更明显。
- 明确指定需要的字段:
User::select('id', 'name', 'email', 'updated_at')->get() - API 返回用 Resource 控制输出,重写
toArray()剔除冗余项 - 对长字段做条件加载,比如加参数
?include_content=1才查正文
加索引,让 WHERE 和 JOIN 快起来
没索引的查询,尤其是带 WHERE status = ?、JOIN on user_id 或 ORDER BY created_at 的,很容易全表扫描。
- 外键字段必须手动加索引(Laravel 不自动建):
$table->index('user_id') - 高频查询条件组合建复合索引,等值字段放前,范围字段(如
created_at > ?)放后 -
softDeletes的deleted_at字段也要索引,否则withTrashed()或onlyTrashed()很慢 - 用
EXPLAIN验证索引是否被命中,别只看迁移有没有跑成功
分页别用 offset,换成游标分页
当数据量上十万,paginate(20) 越往后翻越慢,因为 MySQL 每次都要跳过前面几万行。游标分页基于排序字段值定位,性能稳定。
- 确保排序字段(如
id或created_at)有索引 - 控制器改用
cursorPaginate(50),返回带next_cursor的结构 - 前端下次请求带上这个游标值,不能自己拼、不能解码修改











