你好!我是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
阅读更多: