Laravel优化Eloquent模型怎么用_LaravelEloquent模型的优化介绍【介绍】
优化Eloquent查询比加索引更关键,应避免N+1查询并精准预加载关联字段。使用toArray()或toJson()会遍历整个模型树,建议直接select特定字段。软删除需手动添加deleted_atISNULL约束并加索引。游标分页要求排序字段唯一且有索引,不支持跳页。
在日常的 API 性能排查中,经常遇到一个现象:代码逻辑看着没问题,但数据量一大就卡住。很多人的第一反应是“加索引”。说实话,索引确实有用,但很多时候,问题并不在数据库那端。先聊几个在实际调优中必须明确的关键点。

什么时候该考虑优化 Eloquent 查询,而不是硬加索引?
多数性能问题的根源其实不在数据库,恰恰是那些“看不见”的 N+1 查询,或者反复加载同一个模型。想象一下,在循环里不断调用 $user->posts,却忘了用 with() 预加载——这会导致几十次 SQL 查询。相比之下,这种优化比单纯加个 INDEX 能带来更显著的性能提升。
这里有几个务实的做法:
- 先把问题解剖开。用 Lara vel Telescope 或者
DB::listen()把运行中的 SQL 全部抓出来。看看是不是每次循环都在偷偷执行一条关联查询——这才是问题的关键。 - 预加载要精准,而不是一股脑全加载。只对真正频繁调用的关联字段进行预加载,别无脑写
with(['posts', 'profile', 'settings'])。多余的字段不仅浪费内存,还会拖慢序列化过程。 - 如果只是需要计数,直接用
$user->posts()->count()。千万别先get()全部数据,再调用count(),那完全是绕远路。
toArray() 和 toJson() 为什么让 API 变慢?
这两个方法看起来很方便,但背后隐藏着不小的性能代价。它们会强制遍历所有的 Attribute、Cast 以及 Appends,还会递归处理所有的关联模型——哪怕你只想要两三个字段,它也会把整个模型树走一遍。
一个更高效的策略是:
- 当 API 需要返回固定字段时,直接用
select('id', 'name', 'email')配合get(),然后手动map()成数组。这样能完全跳过 Eloquent 的序列化开销,速度会快很多。 - 避免在
toArray()中嵌入耗时逻辑,比如文件路径拼接或远程 API 调用。这些应该放在资源类(Resource)或服务层处理。 - 如果模型中使用了
$casts,并且某个字段值是 JSON 字符串,那么每次调用toArray()都会重复执行json_decode。数据量大时,这绝对是个性能瓶颈。
软删除(SoftDeletes)不加 WHERE deleted_at IS NULL 就等于没删
很多人以为启用 SoftDeletes 后,Eloquent 会自动过滤掉已软删除的记录。这是个常见的误解。实际上,除非显式调用 withTrashed() 或 onlyTrashed(),否则 find()、first()、get() 这些操作完全不会自动加上 deleted_at IS NULL 的约束。
需要警惕的几个点:
- 全局作用域是保证软删除生效的关键。虽然 Lara vel 默认已经为 Eloquent 模型注册了
SoftDeletingScope,但如果你写的是原生查询或使用DB::table(),那这个作用域就完全不起作用了。 - 在写原生 SQL 或使用
DB::select()时,必须手动加上AND deleted_at IS NULL。否则,软删除功能形同虚设,被“删除”的数据依然会出现在查询结果中。 - 迁移文件中添加了
$table->softDeletes(),别忘了给deleted_at字段加索引。否则,随着数据量增长,带有软删除功能的分页查询会变得越来越慢。
用 cursorPaginate() 替代 paginate() 的真实代价
cursorPaginate() 确实能绕过传统 OFFSET 带来的性能陷阱,但这把刀有其使用前提。它要求排序字段必须严格唯一、非空、且有索引。用 id 做排序没问题,但如果用 created_at,遇到毫秒级重复值时,就容易出问题。
务实的做法是:
- 确保
orderBy字段的组合能够唯一确定一行记录。推荐使用orderBy('id')或者orderBy('created_at', 'id')。 - 避免同时使用
where和模糊查询(比如LIKE),并指望游标分页稳定运行。一旦索引失效,结果很可能出现错乱或漏数据。 - 前端传来的
cursor参数是经过 Base64 编码的上一页最后一条记录的排序字段值,不要直接把它当 ID 解析。解码后一定要校验格式,防止潜在的安全风险或空指针异常。
最容易被忽略的一点是:游标分页不支持跳转到任意页码(比如“跳到第 23 页”),也无法提供 lastPage() 这样的总页数信息。如果业务逻辑强依赖这些功能,硬切游标分页反而会增加不必要的复杂度。


































