侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

如何在PHP中检测n+1查询问题及场景示例

php  /  管理员 发布于 1年前   293

什么是 n+1 查询问题?

n+1 查询问题是软件开发中常见的性能问题。N+1 查询会导致许多不必要的数据库调用。

这会导致应用程序以蜗牛般的速度运行,尤其是当数据增长时。

因此,您必须了解并解决 N+1 查询问题,以确保您的应用程序高效、反应灵敏并具有可扩展性。


当应用程序进行一次数据库查询以检索一个对象,然后对检索到的每个对象进行其他查询

以获取相关对象时,就会出现 N+1 查询。

这样,N 个对象总共要执行 N+1 次数据库查询,这会大大降低应用程序的效率和性能,

尤其是在处理大型数据集时。


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

https://scoutapm.com/?utm_source=laravelnews&utm_medium=affiliate&utm_campaign=03_24&utm_content=n1_queries_in_php


n+1 查询示例:

让我们考虑一个需要显示作者及其书籍列表的书店应用程序。

应用程序可能首先查询数据库以检索所有作者(1 次查询)。

然后,针对检索到的每位作者,再进行一次查询,获取他们各自的书籍。

如果有 100 位作者,则总共需要 1 次查询(初始查询)+ 100 次查询(每位作者一次)= 101 次查询。


这样做效率很低,会严重降低应用程序的性能。


为了避免 N+1 查询问题,开发人员通常使用急切加载(在初始数据库查询中加载相关数据)或

https://sequelize.org/docs/v6/advanced-association-concepts/eager-loading/

批量获取(批量检索多个对象的相关数据)等技术。

https://stackoverflow.com/questions/8241822/php-mysql-running-query-in-batches
http://www.seancolombo.com/2009/07/05/quick-tip-do-huge-mysql-queries-in-batches-when-using-php/


如何检测 n+1 查询?

如果你有一个非同小可的应用程序,你很可能会有 n+1 查询。

如果你的应用程序是使用 Laravel 或 Symfony 等网络框架构建的,并且使用了 ORM,

那么肯定会有很多 n+1 查询。

这是因为许多现代网络框架的 ORM 层默认会懒加载记录。


在开发和测试环境中,N+1 查询问题很可能不会被注意到。

但是,当部署到生产环境时,这些问题可能会突然破坏应用程序的性能,

因为生产环境中数据库的行数要高得多。

应用程序存在 n+1 查询的主要标志是正在执行的数据库查询次数异常多。

这些查询通常是顺序进行且不重叠的。


这一切都很好,但你可能会问:

"好吧,但我怎么知道执行了多少次查询,以及这些查询是否'连续且不重叠'?

问得好!这就是应用程序性能监控 (APM) 工具的用武之地。


使用 APM 工具查找 n+1 查询:

应用程序性能监控工具,顾名思义,就是您可以用来监控和诊断应用程序问题的应用程序。

此类工具可以帮助您监控各种性能指标,包括数据库查询。


有许多不同的 APM 工具可供选择。

在本例中,我将使用 Scout APM。

https://scoutapm.com/?utm_source=laravelnews&utm_medium=affiliate&utm_campaign=03_24&utm_content=n1_queries_in_phpoutapm.com/

1.png


Scout APM 让查找 n+1 查询变得非常简单,因为它有一个专门的 n+1 insights 标签。

n+1 insights 标签会显示应用程序中所有端点的列表(红色高亮显示),对于每个端点,

你都可以看到运行了多少次查询以及查询花费了多长时间。点击单个端点将显示更深入的信息。

2.png


在端点详细信息页面上,你可以看到一个非常有用的时间轴视图,

例如,在部署更新后,你可以看到 n+1 出现的时间。

上面的截图描述的是浓缩视图,它能让你更直观地了解 n+1 查询的情况。

点击这里的 SQL 按钮(蓝色高亮显示),Scout 会向你显示值得指责的 SQL 查询。


回溯

在端点详细信息页面,你可以单击回溯按钮,找到 n+1 查询的确切代码行。

3.png


从上图中可以看到,Scout 追溯到了有问题的代码,准确找到了导致问题的行。

显示代码片段是因为我使用了 Scout 的 GitHub 集成。

https://scoutapm.com/docs/integrations

不过,即使你没有 GitHub 集成,Scout 仍会报告出问题代码文件和行号。

既然我们知道了如何使用 APM 追查 n+1 查询,那么接下来我们就来解决它们。


解决 n+1 查询

为了说明如何在使用 ORM(如 Laravel 的 Eloquent 或 Doctrine)的 PHP 应用程序中

解决 N+1 查询问题,我们将重温前面提到的基于书店应用场景的示例。


情景:

数据库中有两个表:作者和书籍。每个作者可以拥有多本书。

让我们先看看导致 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 查询问题的一种方法是使用一种称为急切加载(eager loading)的策略。

通过急切加载,您可以在初始查询中加载相关模型(在本例中为书籍)。

// 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 语句在单个查询中获取所有内容。

https://www.w3schools.com/sql/sql_join.asp

您可以使用原始查询或框架提供的查询生成器来实现这一点。


原始查询示例(使用 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 查询问题的另一种方法是使用缓存。

https://www.prisma.io/dataguide/managing-databases/introduction-database-caching

缓存为 n+1 查询问题提供了一种策略性解决方案,尤其是在数据变化不频繁的情况下。

通过将数据库查询结果存储在缓存中,后续请求可以从缓存中检索数据,而无需再次访问数据库。

这大大减少了对数据库的查询次数,尤其是重复请求。

需要注意的是,您可以将缓存与前一种解决方案结合使用。


PHP 中的 N+1 查询: 结论

了解并处理 n+1 查询对于优化 PHP 应用程序至关重要。

我们探讨了什么是 n+1 查询、它们如何悄无声息地降低应用程序性能,

以及使用 Scout APM 等工具检测 n+1 问题的策略。


解决 n+1 查询不仅仅是为了提高速度,而是为了编写更智能、更高效的代码。

通过应用这些见解,您可以确保为用户提供更流畅、更快速的体验。


  • 上一条:
    分库分表的目的、优缺点及具体实现方式介绍
    下一条:
    在Laravel 11中的精简配置文件介绍
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客