通过优化ORM性能使您的应用程序可扩展

本文的翻译是在“ PHP后端开发人员”课程开始的前夕准备的










你好!我是Valerio,他是该平台的意大利开发人员兼首席技术官Inspector.dev



在本文中,我将分享在开发后端服务时使用的一组ORM优化策略。



我敢肯定,我们每个人都不得不抱怨服务器或应用程序运行缓慢(甚至根本无法运行),而在咖啡机旁却没有时间等待长请求的结果。



如何解决?

让我们找出答案!



数据库是共享资源



为什么数据库会导致如此多的性能问题?

我们经常忘记没有查询是独立于其他查询的。

我们认为,即使某些查询速度很慢,也几乎不会影响其他查询……但是确实如此吗?



数据库是应用程序中运行的所有进程使用的共享资源。即使是一种设计不当的访问数据库的方法,也可能破坏整个系统的性能。



因此,请不要忘记可能的后果,请思考:“可以对这段代码进行优化,这是可以的!” 对数据库的缓慢访问会导致其过载,进而对用户体验产生负面影响。



N + 1个数据库查询问题



N + 1问题是什么?



使用ORM与数据库进行交互时,这是一个常见问题。这与编写SQL代码无关。



当使用像Eloquent这样的ORM系统时,并不总是清楚哪些查询将在何时运行。在这个特定问题的背景下,让我们讨论关系和渴望加载。



任何ORM系统都允许您声明实体之间的关系,并提供用于导航数据库结构的出色API。

以下是“文章”和“作者”实体的一个很好的示例。



/*
 * Each Article belongs to an Author
 */
$article = Article::find("1");
echo $article->author->name; 
/*
 * Each Author has many Articles
 */
foreach (Article::all() as $article)
{
    echo $article->title;
}


但是,在循环中使用关系时,必须仔细编写代码。



看下面的例子。



我们想在文章标题旁边添加作者的名字。使用ORM,您可以使用文章和作者之间的一对一关系来获得作者的姓名。



一切似乎都很简单:



// Initial query to grab all articles
$articles = Article::all();
foreach ($articles as $article)
{
    // Get the author to print the name.
    echo $article->title . ' by ' . $article->author->name;
}


但是后来我们陷入了陷阱!



此循环生成一个初始请求以获取所有文章:



SELECT * FROM articles;


和N个其他查询以获取每篇文章的作者并显示“名称”字段的值,即使作者始终相同。



SELECT * FROM author WHERE id = [articles.author_id]


我们恰好收到N + 1个请求。



这似乎没什么大不了的。好吧,让我们额外提出15或20个请求-没什么大不了的。但是,让我们回到本文的第一部分:



  • — , .
  • , , .
  • , .


:



根据Laravel文档,您很有可能会遇到N + 1查询问题,因为当您将Eloquent关系作为属性($article->author访问时,关系数据会被延迟加载。



这意味着在您首次访问属性之前,不会加载关系数据。



但是,使用一种简单的方法,我们可以一次加载所有关系数据。然后,当作为属性访问Eloquent关系时,ORM系统将不会执行新查询,因为数据已经加载。



此策略称为“紧急加载”,并且受所有ORM支持。



// Eager load authors using "with".
$articles = Article::with('author')->get();
foreach ($articles as $article)
{
    // Author will not run a query on each iteration.
    echo $article->author->name;
}


Eloquent提供了一种with()热切加载关系的方法



在这种情况下,将仅执行两个查询。

首先需要下载所有文章:



SELECT * FROM articles;


第二个将由方法执行with(),并将获取所有作者:



SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);


Eloquent的内部机制将映射数据,并且可以通过通常的方式进行访问:



$article->author->name;


优化您的运营商 select



很长时间以来,我认为显式声明获取查询中的字段数并不会显着提高性能,因此为简单起见,我将查询中的所有字段都包含在内。



另外,在特定的select语句中对字段列表进行硬编码,使得进一步维护此类代码变得更加困难。



该论点的最大缺陷是,从数据库角度来看,这确实是正确的。



但是,我们正在使用ORM,因此将从数据库中选择的数据加载到PHP端的内存中,以便ORM系统将对其进行进一步管理。我们捕获的字段越多,该过程将占用更多的内存。



Laravel Eloquent提供了一个select方法来将查询限制为仅我们需要的列:



$articles = Article::query()
    ->select('id', 'title', 'content') // The fields you need
    ->latest()
    ->get();


通过排除字段,PHP解释器不必处理额外的数据,因此您可以大大减少内存消耗。



避免完全读取还可以提高排序,分组和合并的性能,因为数据库本身可以节省内存。



在MySQL中使用视图



视图是基于其他表的SELECT查询,并存储在数据库中。



当我们选择一个或多个表时,数据库首先编译我们的SQL语句,确保它没有错误,然后获取数据。



视图是一个预编译的SELECT语句,在处理该语句时,MySQL立即执行该视图的基础内部查询。



此外,在过滤数据方面,MySQL通常比PHP聪明。使用视图而不是使用PHP函数来处理集合或数组时,可以显着提高性能。



如果您想了解有关MySQL开发数据库密集型应用程序的功能的更多信息,请访问以下网站:www.mysqltutorial.org



将口才模型链接到视图



视图也称为“虚拟表”。从ORM的角度来看,它们看起来像常规表。



因此,您可以创建一个Eloquent模型来查询视图中的数据。



class ArticleStats extends Model
{
    /**
     * The name of the view is the table name.
     */
    protected $table = "article_stats_view";
    /**
     * If the resultset of the View include the "author_id"
     * we can use it to retrieve the author as normal relation.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}


关系像照常一样起作用,强制,分页等等也一样,并且不影响性能。



结论



我希望这些技巧将帮助您开发更可靠和可扩展的软件。



所有代码示例都是使用Eloquent作为ORM编写的,但请记住,这些策略对于所有主要ORM都是相同的。



我经常说,我们需要工具来实施有效的战略。如果没有战略,那就没有什么可谈的。



非常感谢您阅读本文至结尾。如果您想进一步了解Inspector,我邀请您访问我们的网站www.inspector.dev如有任何疑问,请随时写信给聊天!



先前发布在这里:www.inspector.dev/make-your-application-scalable-optimizing-the-orm-performance





阅读更多:






All Articles