发布于2026-07-07 阅读(0)
扫一扫,手机访问
在日常的 API 性能排查中,经常遇到一个现象:代码逻辑看着没问题,但数据量一大就卡住。很多人的第一反应是“加索引”。说实话,索引确实有用,但很多时候,问题并不在数据库那端。先聊几个在实际调优中必须明确的关键点。

Eloquent 查询,而不是硬加索引?多数性能问题的根源其实不在数据库,恰恰是那些“看不见”的 N+1 查询,或者反复加载同一个模型。想象一下,在循环里不断调用 $user->posts,却忘了用 with() 预加载——这会导致几十次 SQL 查询。相比之下,这种优化比单纯加个 INDEX 能带来更显著的性能提升。
这里有几个务实的做法:
DB::listen() 把运行中的 SQL 全部抓出来。看看是不是每次循环都在偷偷执行一条关联查询——这才是问题的关键。with(['posts', 'profile', 'settings'])。多余的字段不仅浪费内存,还会拖慢序列化过程。$user->posts()->count()。千万别先 get() 全部数据,再调用 count(),那完全是绕远路。toArray() 和 toJson() 为什么让 API 变慢?这两个方法看起来很方便,但背后隐藏着不小的性能代价。它们会强制遍历所有的 Attribute、Cast 以及 Appends,还会递归处理所有的关联模型——哪怕你只想要两三个字段,它也会把整个模型树走一遍。
一个更高效的策略是:
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 的约束。
需要警惕的几个点:
SoftDeletingScope,但如果你写的是原生查询或使用 DB::table(),那这个作用域就完全不起作用了。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() 这样的总页数信息。如果业务逻辑强依赖这些功能,硬切游标分页反而会增加不必要的复杂度。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8