优化Laravel数据库查询的18个技巧_第1页
优化Laravel数据库查询的18个技巧_第2页
优化Laravel数据库查询的18个技巧_第3页
优化Laravel数据库查询的18个技巧_第4页
优化Laravel数据库查询的18个技巧_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第优化Laravel数据库查询的18个技巧如果应用运行缓慢或存在大量数据库查询,请按照以下性能优化提示来缩短应用的加载时间。

1.检索大型数据集

本提示主要侧重于提高处理大型数据集时应用的内存使用率。

处理大的集合时,分组检索结果处理,而不是一次性检索处理。

如下展示了从posts表检索数据的过程。

$posts=Post::all();//使用eloquent

$posts=DB::table(posts)-get();//使用查询构造器

foreach($postsas$post){

//处理posts操作

}

上面的例子会从posts表检索所有的记录并处理。如果这个表达到了100多万行呢?内存将很快被耗尽。

为了避免在处理大型数据集时出现问题,我们可以检索结果子集并按照下面的方式处理它们。

选项1:使用chunk

//当使用eloquent时

$posts=Post::chunk(100,function($posts){

foreach($postsas$post){

//Processposts

//当使用查询构造器时

$posts=DB::table(posts)-chunk(100,function($posts){

foreach($postsas$post){

//Processposts

});

以上例子从posts表中检索100条记录对其进行处理,另外再检索100条记录进行处理。此迭代将继续,直到处理完所有记录。

这种方法将创建更多的数据库查询,但内存效率会更高。通常,大型数据集的处理应该再后台进行。因此,可以在后台运行时进行更多查询,以避免在处理大型数据集时耗尽内存。

选项2:使用游标

//使用eloquent

foreach(Post::cursor()as$post){

//处理单个post

//使用query构建器

foreach(DB::table(posts)-cursor()as$post){

//处理单个post

}

示例进行单个数据库查询,检索表的所有记录,一个接一个一个处理Eloquent模型。这种方式仅查询一次数据库,得到全部posts。但使用php生成器优化内存使用。

什么情况使用这个呢?

这能够在应用层极大地优化内存使用,由于我们检索表的所有数据,数据库内存占用任然很高。

在数据库内存较多,应用内存较少的时候,建议使用游标。然而,如果你的数据库没有足够的内存,最好使用chunks。

选项3:使用chunkById

//使用eloquent

$posts=Post::chunkById(100,function($posts){

foreach($postsas$post){

//处理posts

//使用query构造器

$posts=DB::table(posts)-chunkById(100,function($posts){

foreach($postsas$post){

//处理posts

});

chunk和chunkById最大的区别是chunk通过offset和limit检索数据。然而

chunkById通过id字段检索结构。id字段通常是整型字段,而且它也是自增字段。

chunk和chunkById的查询如下。

chunk

select*frompostsoffset0limit100

select*frompostsoffset101limit100

chunkById

select*frompostsorderbyidasclimit100

select*frompostswhereid100orderbyidasclimit100

通常,查询使用limit和offset是较慢的,尽量避免使用。本文详细介绍使用offset的问题。

chunkById使用id整型字段,通过whereclause查询,这样会更快。

什么时候使用chunkById?

当数据库存在自增主键的时候使用。

2.选择合适的列

通常从数据库检索数据时,会像下面这样做。

$posts=Post::find(1);//使用eloquent

$posts=DB::table(posts)-where(id,=,1)-first();//使用query构建器

上面的代码会得到如下的查询

select*frompostswhereid=1limit1

select*表示从表中查出所有列。

当需要所有列时,这没有问题。

然而,仅需要指定的列(id,title)时,只需要像下面这样检索那些列。

$posts=Post::select([id,title])-find(1);//使用eloquent

$posts=DB::table(posts)-where(id,=,1)-select([id,title])-first();//使用query构建器

上面代码得到如下查询

selectid,titlefrompostswhereid=1limit1

3.当需要数据库表的一两个列时

这点主要关注对检索结果的处理时间。这不影响实际的查询时间。

如我上面提到的,检索指定的列,可以这样做

$posts=Post::select([title,slug])-get();//使用eloquent

$posts=DB::table(posts)-select([title,slug])-get();//使用query构建器

执行上面的代码,它会在幕后执行以下操作。

执行selecttitle,slugfromposts查询

检索出的每一行对应一个Post模型对象(对PHP对象)(query构建器得到标准的PHP对象)

为Post模型生成collection

返回collection

访问数据

foreach($postsas$post){

//$post是Post模型或php标准对象

$post-title;

$post-slug;

}

上面的方式有额外的开销,为每一行创建Post模型,并为这些对象创建一个集合。如果的确需要Post模型实例而不是数据,这是最正确的做法。

但如果您只需要两个值时,则可以执行以下操作:

$posts=Post::pluck(title,slug//使用eloquent时

$posts=DB::table(posts)-pluck(title,slug//使用查询构造器时

当上面代码被执行时,它在幕后会执行以下操作。

对数据库执行selecttitle,slugfromposts查询

创建一个数组,其中会以title作为数组值,slug作为数组键

返回数组(数组格式:[slug=title,slug=title])

要访问结果,我们可以这么做

foreach($postsas$slug=$title){

//$title是post的title

//$slug是post的slug

}

如果您想检索一列,您可以这么做

$posts=Post::pluck(title//使用eloquent时

$posts=DB::table(posts)-pluck(title//使用查询构造器时

foreach($postsas$title){

//$title是post的title

}

上面的方式消除了每一行Post对象的创建。这将降低查询结果处理的内存和时间消耗。

建议在新代码中使用上述方式。个人感觉不值得花时间遵循上面的提示重构代码。

重构代码,最好是在要处理大的数据集或者是比较闲的时候

4.使用查询代替collection来统计行数

统计表的行数,通常这样做

$posts=Post::all()-count();//使用eloquent

$posts=DB::table(posts)-get()-count();//使用查询构造器

这将生成以下查询

select*fromposts

上述方法将从表中检索所有行。将它们加载到collection对象中并计算结果。当数据表中的行较少时,这可以正常工作。但随着表的增长,内存很快就会耗尽。

与上述方法不同,我们可以直接计算数据库本身的总行数。

$posts=Post::count();//使用eloquent时

$posts=DB::table(posts)-count();//使用查询构造器时

这将生成以下查询

selectcount(*)fromposts

在sql中计算行数是一个缓慢的过程,当数据库表中有多行时性能会很差。最好尽量避免计算行数。

5.通过即时加载关系避免n+1查询

这条建议你可能听说过无数次了。所以我会尽可能简短。让我们假设您有以下场景

classPostControllerextendsController

publicfunctionindex()

$posts=Post::all();

returnview(posts.index,[posts=$posts]);

}

//posts/index.blade.php文件

@foreach($postsas$post)

h3{{$post-title}}/h3

pAuthor:{{$post-author-name}}/p

/li

@endforeach

上面的代码是检索所有的帖子,并在网页上显示帖子标题和作者,假设帖子模型关联作者。

执行以上代码将导致运行以下查询。

select*fromposts//假设返回5条数据

select*fromauthorswhereid={post1.author_id}

select*fromauthorswhereid={post2.author_id}

select*fromauthorswhereid={post3.author_id}

select*fromauthorswhereid={post4.author_id}

select*fromauthorswhereid={post5.author_id}

如上,1条查询来检索帖子,5条查询来检索帖子的作者(假设有5篇帖子)。因此对于每篇帖子,都会进行一个单独的查询来检索它的作者。

所以如果有N篇帖子,将会产生N+1条查询(1条查询检索帖子,N条查询检索每篇帖子的作者)。这常被称作N+1查询问题。

避免这个问题,可以像下面这样预加载帖子的作者。

$posts=Post::all();//Avoiddoingthis

$posts=Post::with([author])-get();//Dothisinstead

执行上面的代码得到下面的查询:

select*fromposts//Assumethisqueryreturned5posts

select*fromauthorswhereidin({post1.author_id},{post2.author_id},{post3.author_id},{post4.author_id},{post5.author_id})

6.预加载嵌套关系

从上面的例子,考虑作者归属于一个组,同时需要显示组的名字的情况。因此在blade文件中,可以按下面这样做。

@foreach($postsas$post)

h3{{$post-title}}/h3

pAuthor:{{$post-author-name}}/p

pAuthorsTeam:{{$post-author-team-name}}/p

/li

@endforeach

接着

$posts=Post::with([author])-get();

得到下面的查询:

select*fromposts//Assumethisqueryreturned5posts

select*fromauthorswhereidin({post1.author_id},{post2.author_id},{post3.author_id},{post4.author_id},{post5.author_id})

select*fromteamswhereid={author1.team_id}

select*fromteamswhereid={author2.team_id}

select*fromteamswhereid={author3.team_id}

select*fromteamswhereid={author4.team_id}

select*fromteamswhereid={author5.team_id}

如上,尽管预加载了authors关系,仍然产生了大量的查询。这是因为没有预加载authors上的team关系。

通过下面这样来解决这个它。

$posts=Post::with([author.team])-get();

执行得到下面的查询。

select*fromposts//Assumethisqueryreturned5posts

select*fromauthorswhereidin({post1.author_id},{post2.author_id},{post3.author_id},{post4.author_id},{post5.author_id})

select*fromteamswhereidin({author1.team_id},{author2.team_id},{author3.team_id},{author4.team_id},{author5.team_id})

通过预加载嵌套关系,可以将查询数从11减到3。

7.如果仅需要id时,别预加载belongsTo关系

想象一下,有posts和authors两张表。帖子表有author_id列归属作者表。

为了得到帖子的作者id,通常这样做

$post=Post::findOrFAIl(postid

$post-author-

执行得到两个查询。

select*frompostswhereid=postidlimit1

select*fromauthorswhereid=postauthoridlimit1

然而,可以直接通过下面方式得到作者id。

$post=Post::findOrFail(postid

$post-author_id;//帖子表有存放作者id的author_id列

什么时候采取上面的方式?

采取上的方式,需要确保帖子关联的作者在作者表始终存在。

8.避免使用不必要的查询

很多时候,一些数据库查询是不必要的。看看下面的例子。

php

classPostControllerextendsController

publicfunctionindex()

$posts=Post::all();

$private_posts=PrivatePost::all();

returnview(posts.index,[posts=$posts,private_posts=$private_posts]);

}

上面代码是从两张不同的表(posts,private_posts)检索数据,然后传到视图中。

视图文件如下。

//posts/index.blade.php

@if(request()-user()-isAdmin())

h2PrivatePosts/h2

@foreach($private_postsas$post)

h3{{$post-title}}/h3

pPublishedAt:{{$post-published_at}}/p

/li

@endforeach

/ul

@endif

h2Posts/h2

@foreach($postsas$post)

h3{{$post-title}}/h3

pPublishedAt:{{$post-published_at}}/p

/li

@endforeach

/ul

正如你上面看到的,$private_posts仅对管理员用户可见,其他用户都无法看到这些帖子。

问题是,当我们在做

$posts=Post::all();

$private_posts=PrivatePost::all();

我们进行两次查询。一次从posts表获取记录,另一次从private_posts表获取记录。

private_posts表的记录仅管理员用户可见。但我们仍在查询以检索所有用户记录,即使它们不可见。

我们可以调整逻辑,避免额外的查询。

$posts=Post::all();

$private_posts=collect();

if(request()-user()-isAdmin()){

$private_posts=PrivatePost::all();

}

将逻辑更改为上述内容后,我们对管理员用户进行了两次查询,并对其他用户进行了一次查询。

9.合并相似的查询

我们有时需要进行查询以同一个表中检索不同类型的行。

$published_posts=Post::where(status,=,published)-get();

$featured_posts=Post::where(status,=,featured)-get();

$scheduled_posts=Post::where(status,=,scheduled)-get();

上述代码正从同一个表检索状态不同的行。代码将进行以下查询。

select*frompostswherestatus=published

select*frompostswherestatus=featured

select*frompostswherestatus=scheduled

如您所见,它正在对同一个表进行三次不同的查询以检索记录。我们可以重构此代码以仅进行一次数据库查询。

$posts=Post::whereIn(status,[published,featured,scheduled])-get();

$published_posts=$posts-where(status,=,published

$featured_posts=$posts-where(status,=,featured

$sc

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论