在 Laravel 中排查 N+1 问题并消除无效查询

性能优化工作通常是枯燥的。

它并不总是关于那种将延迟降低 80% 的英雄事迹。大多数时候,它关乎清理。你会发现一些你甚至不知道正在运行的查询,然后将其删除。

如果你的 Laravel 应用正在增长,你很可能正面临以下三个问题。

  1. 隐藏的 N+1 查询

看不见就无法修复。在本地环境中使用像 beyondcode/laravel-query-detector 这样的工具。它会在你使用“循环+延迟加载”模式时提醒你。

不要将其运行在生产环境中,因为它会增加额外开销。仅在应用处于 local 或 testing 模式时才注册它。

  1. 预加载(Eager Loading)造成的浪费

人们通常通过添加 with() 来修复 N+1 问题。但这会产生一个新问题:你可能会预加载一个视图不再使用的关联关系。

如果重新设计移除了表中的某个列,你必须移除控制器中相应的 with() 调用。你正在为丢弃的数据支付代价。

将每一次预加载视为一种“声明”。如果你不使用这些数据,就撤销该声明。

  1. 重复计算相同的数据

某些值在整个请求过程中保持不变。如果你多次计算它们,就会浪费 CPU。使用记忆化(memoization)来解决这个问题。

示例:

protected ?string $defaultConnection = null;

public function getDefaultConnectionName(): string
{
    return $this->defaultConnection ??= $this->resolveDefaultConnection();
}

??= 运算符会计算一次该值,并在请求的剩余部分中重复使用它。

  1. 仪表盘查询陷阱

仪表盘通常会为每一个卡片运行一个独立的 count() 查询。如果你有六个卡片,就会运行六次查询。

改为执行以下两件事:

  • 聚合你的计数。通过单个查询获取一张表的所有汇总数据。
  • 使用短时间的缓存。仪表盘统计数据不需要精确到秒。将它们缓存五分钟。

一个访客承担查询成本,其他所有人都能获得缓存结果。

最终建议:防止回归

如果开发人员随后重新添加了错误的代码,清理工作就会失败。使用 Pest 来断言你的查询数量。为页面可以运行的查询数量设置一个上限。如果开发人员添加了 N+1,测试将会失败。

优化在很大程度上在于移除。移除未使用的加载。移除重复计算的值。移除不必要的查询。

Source: https://dev.to/nasrulhazim/a-day-of-performance-hardening-hunting-n1s-and-killing-wasted-queries-in-laravel-568p