如何在 PHP 中检测 N+1 查询

发布日期:作者:

How to Detect n+1 Queries in PHP image

什么是 N+1 查询问题?

N+1 查询问题是软件开发中常见的性能问题。N+1 查询会导致许多不必要的数据库调用。这会导致您的应用程序运行速度缓慢,尤其是在数据量增长的情况下。因此,您必须了解并解决 N+1 查询,以确保您的应用程序高效、响应迅速且可扩展。

当应用程序发出一个数据库查询来检索一个对象,然后为检索到的每个对象发出额外的查询来获取相关对象时,就会出现 N+1 查询。这会导致总共执行 N+1 个数据库查询来获取 N 个对象,这会严重降低应用程序的效率和性能,尤其是在处理大型数据集时。

本文将探讨如何使用 应用程序性能监控 (APM) 工具 快速检测和解决 N+1 查询。

N+1 查询示例

让我们考虑一个需要显示作者及其书籍列表的书店应用程序。应用程序可能首先查询数据库以检索所有作者(1 个查询)。然后,为检索到的每个作者,它发出另一个查询来获取他们各自的书籍。如果有 100 个作者,则这将导致 1(初始查询)+ 100(每个作者一个)= 101 个查询。

这是非常低效的,并且会严重降低应用程序的性能。

为了避免 N+1 查询问题,开发人员通常使用诸如 预加载 之类的技术,在初始数据库查询中加载相关数据,或者使用 批量获取,在其中 批量获取 多个对象的关联数据。

如何检测 N+1 查询?

如果您有一个非平凡的应用程序,您可能会有 N+1 查询。如果您的应用程序使用 Laravel 或 Symfony 等 Web 框架构建,并使用 ORM,您肯定会遇到许多 N+1 查询。这是因为许多现代 Web 框架的 ORM 层默认情况下会延迟加载记录。

N+1 查询问题可能会在您的开发和测试环境中被忽视。但是,当部署到生产环境时,它们可能会突然破坏应用程序的性能,因为数据库中的行数要多得多。

应用程序存在 N+1 查询的明显迹象是执行的数据库查询数量异常多。

这些查询通常是顺序的,并且没有重叠。

这很好,但是您可能想知道,“好吧,但是我怎么知道执行了多少查询以及它们是否‘顺序且没有重叠’?” 这是一个好问题!这就是应用程序性能监控 (APM) 工具发挥作用的地方。

使用 APM 工具查找 N+1 查询

顾名思义,应用程序性能监控工具是您可以用来监控和诊断应用程序问题的应用程序。这些工具可以帮助您监控各种性能指标,包括数据库查询。

有许多不同的 APM 工具可用。在本示例中,我将使用 Scout APM

Scout APM 使查找 N+1 查询变得非常简单,因为它具有专门的 N+1 洞察力选项卡。N+1 洞察力选项卡显示应用程序中所有端点的列表(以红色突出显示),并且对于每个端点,您可以看到运行了多少个查询以及它们花费了多长时间。单击单个端点将显示更深入的信息。

在端点详细信息页面上,您可以获得一个非常有用的时间线视图,您可以在其中看到 N+1 何时出现,例如,在部署更新之后。上面的屏幕截图描绘了显示对 N+1 查询发生情况的简化洞察力的浓缩视图。单击此处突出显示的蓝色 SQL 按钮,Scout 将显示有问题的 SQL 查询。

回溯

在端点详细信息页面上,您可以单击回溯按钮来查找导致 N+1 查询的代码的确切行。

您可以在上面的图像中看到,Scout 将有问题的代码追溯到导致问题的代码行。代码片段显示出来了,因为我使用的是 Scout 的 GitHub 集成。但是,即使您没有使用 GitHub 集成,Scout 仍然会报告有问题的代码文件和行号。

现在我们知道了如何使用 APM 查找 N+1 查询,让我们继续解决它们。

解决 N+1 查询

为了说明如何在使用 ORM(如 Laravel 的 Eloquent 或 Doctrine)的 PHP 应用程序中解决 N+1 查询问题,我们将重新审视之前提到的基于书店应用程序场景的示例。

场景

您有一个具有两个表的数据库:authors和 books。每个作者可以拥有多本书。让我们首先看看导致 N+1 查询问题的代码,然后我将展示如何解决它。

具有 N+1 查询问题的代码

// Retrieve all authors
$authors = Author::all();
 
foreach($authors as $author) {
   // For each author, retrieve their books$books = $author->books()->get(); // This causes an additional query for each author
   // Process the books...
}

在此代码中,第一个查询检索所有作者,然后为每个作者执行一个新的查询来获取他们的书籍,从而导致 N+1 查询。

N+1 查询解决方案 1:预加载

解决 N+1 查询问题的一种方法是使用称为预加载的策略。使用预加载,您将在初始查询中加载相关模型(在本例中为 books)。

// Eager load books with authors in one query
$authors = Author::with('books')->get();
 
foreach($authors as $author) {
   // Now, no additional query is made here
   $books = $author->books;
   // Process the books...
}

N+1 查询解决方案 2:联接查询

有时,您可能希望使用 JOIN 语句 在单个查询中获取所有内容。您可以使用框架提供的原始查询或查询构建器来执行此操作。

原始查询示例(使用 PDO)

$sql = "SELECT * FROM authors JOIN books ON authors.id = books.author_id";
$stmt = $pdo->query($sql);
$authorsAndBooks = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Process the books accordingly

查询构建器示例(在 Laravel 中)

$authorsAndBooks = DB::table('authors')
   ->join('books', 'authors.id', '=', 'books.author_id')
   ->get();
 
// Process the results accordingly

使用原始查询或查询构建器使您可以编写更优化的 SQL 查询,以通过对数据库的单个请求来获取所需的数据。

N+1 查询解决方案 3:缓存

另一种解决 N+1 查询问题的方法是使用缓存。缓存为 N+1 查询问题提供了一种策略性解决方案,尤其是在数据不经常更改的情况下。通过将数据库查询结果存储在缓存中,后续请求可以从该缓存中检索数据,而不是再次访问数据库。这显着减少了对数据库发出的查询次数,尤其是对于重复请求。

请注意,您可以将缓存与之前任何一种解决方案一起使用。

PHP 中的 N+1 查询:结论

了解并解决 N+1 查询对于优化 PHP 应用程序至关重要。我们已经探讨了什么是 N+1 查询,它们如何悄无声息地降低应用程序性能以及如何使用 Scout APM 等工具来检测 N+1 问题。

解决 N+1 查询不仅仅是提高速度,它还关乎编写更智能、更高效的代码。通过应用这些见解,您可以为用户提供更流畅、更快的体验。

Sarah Morgan photo

Sarah 在软件行业拥有超过 18 年的经验。除了目前担任 Scout 的高级产品经理外,她还是 Product School 的特邀演讲嘉宾,以及 Tapple.io 的产品策略顾问。

归档于
Cube

Laravel 新闻

加入 40,000 多名其他开发者,绝不错过新的技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

毫无妥协

Joel 和 Aaron,来自“毫无妥协”播客的两名经验丰富的开发者,现在可供您的 Laravel 项目聘用。⬧ 固定费率 7500 美元/月。⬧ 没有冗长的销售流程。⬧ 没有合同。⬧ 100% 退款保证。

毫无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧版本的 Laravel?即时、自动化的 Laravel 升级和代码现代化,以保持您的应用程序更新。

Shift
Bacancy logo

Bacancy

使用经验丰富的 Laravel 开发人员为您的项目增效,这些开发人员拥有 4-6 年的经验,每月仅需 2500 美元。获得 160 小时的专用专业知识和无风险的 15 天试用期。立即安排电话会议!

Bacancy
Lucky Media logo

Lucky Media

现在就获得好运 - 拥有十多年经验,是 Laravel 开发的理想选择!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar:Laravel 电子商务

Laravel 的电子商务。一个开源包,将现代无头电子商务功能的强大功能带入 Laravel。

Lunar:Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 招聘平台

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit:Laravel SaaS 启动套件

SaaSykit 是一个 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。付款、漂亮结账、管理面板、用户仪表板、身份验证、即用型组件、统计信息、博客、文档等等。

SaaSykit:Laravel SaaS 启动套件
Rector logo

Rector

您无缝升级 Laravel、降低成本和加速创新的合作伙伴,助力公司取得成功

Rector
MongoDB logo

MongoDB

通过强大的 MongoDB 和 Laravel 集成增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务性、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。探索灵活、现代的 MongoDB 数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包将评论添加到您的 Laravel 应用程序

阅读文章