laravel mysql表优化_Laravel 性能优化:优化 ORM 性能使应用程序高可用

大家好,我是Valerio,来自意大利的软件工程师,也是Inspector的CTO。

在本文中,我将分享一套我正在几乎所有后端服务中使用的ORM优化策略。

我确信我们每个人都会抱怨机器或应用程序运行缓慢甚至死机,然后花时间在咖啡机上等待长时间运行的查询结果。

我们该如何解决?

开始吧!

数据库是共享资源

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

我们经常忘记每个请求都不独立于其他请求。

如果一个请求很慢,似乎会影响其他请求...对吗?

同时在应用程序中运行的所有进程都使用数据库。即使只有一个设计不当的访问也可能会危害整个系统的性能。

因此,请谨慎看待「不优化代码也是可以的」。缓慢的数据库访问可能会使数据库紧张,从而给用户带来负面的体验。

N+1 个数据库查询问题

N + 1 问题是什么?

这是使用ORM与数据库进行交互时遇到的一个典型问题。这不是SQL编码问题。

当您使用Eloquent之类的ORM时,它并不总是很清楚将进行什么查询以及何时进行查询。对于这个特定问题,我们谈论关系和饥饿加载(预加载)。

任何ORM都允许您声明实体之间的关系,从而提供一个出色的API来导航我们的数据库结构。

「文章和作者」是一个很好的例子。

/*

* 每篇文章都属于一个作者

*/

$article = Article::find("1");

echo $article->author->name;

/*

* 每个作者有多个文章

*/

foreach (Article::all() as $article)

{

echo $article->title;

}

但是我们需要谨慎的在循环中使用关联关系。

看下面的例子。

我们要在文章标题旁边添加作者的名字。多亏了ORM,我们可以导航Article与Author之间的一对一关系以获取其名称。

听起来真的很简单:

// 初始查询以获取所有文章

$articles = Article::all();

foreach ($articles as $article)

{

// 获取作者对象以便于打印作者名字

echo $article->title . ' by ' . $article->author->name;

}

我们陷入了陷阱。

此循环生成1个初始查询以获取所有文章:译者注: 原文似乎有表达错误,应该是 「此函数生成一个初始查询以获取所有文章」而不是loop(循环)。

SELECT * FROM articles;

然后 N 个查询来获得文章的作者以便打印作者的「名字」字段。如果作者名字是一样的也是如此。

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

恰好 N+1 个查询。

看起来好像没有这么重要的问题。 十五或二十个问题可能看起来不是一个需要立即解决的问题。 请仔细阅读本文的第一部分:数据库是所有进程共享的资源。

数据库计算机资源有限,或者如果使用托管服务,则更多的数据库负载可能意味着更多的成本。

如果您的数据库位于单独的计算机上,则所有数据都需要以额外的网络延迟进行传输。

[解决方案]使用预加载

如 Laravel documentation 所述, 我们很容易陷入 N + 1 的查询问题, 因为在访问Eloquent关联作为属性时 ($article->author), 关联数据为「延迟加载」. 这意味着关联数据在你第一次访问该属性的时候,才会真正的加载

然而,我们可以用一种简单的方法来加载所有关联数据,所以,当你以属性的方式访问Eloquent关联时,它不会运行新的查询,因为ORM已经加载了该数据。

这种策略称之为「预加载」,所有的ORM都支持此策略

// 作者使用「with」进行预加载.

$articles = Article::with('author')->get();

foreach ($articles as $article)

{

// 作者不会在每次迭代中运行查询。

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') // 只获取你需要的字段

->latest()

->get();

排除字段PHP不必处理此数据,因此可以显着减少内存消耗。

不选择所有内容还可以改善排序,分组和连接的性能,因为数据库可以以这种方式节省内存。

使用 MySQL 视图

视图是在其他表的顶部生成并存储在数据库中的select查询。

在执行 SELECT 查询的时候,Laravel 框架会将您的查询转换为 SQL 查询,当确认没有错误的时候再执行它。

Mysql 视图是一个预编译过的 SQL 查询,mysql 可以直接运行该查询。

在数据筛选方面,使用 mysql 查询的效率要比 php 更高。

在 Eloquent Model 中添加 Mysql 视图。

Mysql 视图是一个虚拟的表,但是 Eloquent ORM 会以普通表的形式处理他。

这就是为什么我们可以通过 Eloquent ORM 直接操作他。

class ArticleStats extends Model

{

/**

* Mysql 视图名称

*/

protected $table = "article_stats_view";

/**

* 如果视图结果中存在 "author_id" 字段

* 我们可以通过它直接找到 Auth 的数据关联。

*/

public function author()

{

return $this->belongsTo(Author::class);

}

}

表关联,分页查询都可以像普通 Eloquent ORM 一样操作,并没有什么不同。

总结

先到这里,希望以上文章可以给您的产品开发带来方便或者启发。

我曾经使用 Eloquent ORM 编写过的事例代码,其中的一些 Eloquent ORM 实现方式,同样适用于您的代码。

常言道,工欲善其事,必先利其器。

感谢您的阅读,如果想要了解更多 Inspector信息,请访问 https://www.inspector.dev.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值