相关文章推荐
$posts = Post::all(); // when using eloquent
$posts = DB::table('posts')->get(); // when using query builder
foreach ($posts as $post){
 // Process posts

以上的例子查询和处理posts中中的所有记录,万一表中有100万条数据呢?内存很快就会用光。

为了避免大量数据集处理的问题,我们可以按照以下方式查询子集、处理结果。

选项1:使用chunk

// when using eloquent
$posts = Post::chunk(100, function($posts){
    foreach ($posts as $post){
     // Process posts
// when using query builder
$posts = DB::table('posts')->chunk(100, function ($posts){
    foreach ($posts as $post){
     // Process posts

以上示例先从posts表中查询处理100条记录,然后再查询另外100条,再处理他们。如此往复迭代,直到所有数据都处理完。

此方式会创建更多的数据库查询,但也会更有效率地使用内存。通常,大量数据的处理应该放在后台。因此,当处理大量数据时,在后台使用更多的查询以避免内存耗尽。

选项2:使用cursor

// when using eloquent
foreach (Post::cursor() as $post){
   // Process a single post
// when using query builder
foreach (DB::table('posts')->cursor() as $post){
   // Process a single post

以上示例会发起单条数据库查询,从表中查询所有记录,一条条进入Eloquent 模型。该方法只会发起一条数据库查询来查询所有记录。但是,使用PHP 生成器可以优化内存使用。

什么时候使用这个?

尽管这会在应用级别上极大优化了内存使用,由于查询了表中的所有记录,数据库中的内存使用依然很高。

如果你的应用拥有的内存较少,数据库实例有较多的内存时,可以使用cursor。如果你的数据库没有足够的内存,最好还是使用chunks。

选项3:使用chunkById

// when using eloquent
$posts = Post::chunkById(100, function($posts){
    foreach ($posts as $post){
     // Process posts
// when using query builder
$posts = DB::table('posts')->chunkById(100, function ($posts){
    foreach ($posts as $post){
     // Process posts

chunk 和 chunkById 最大的不同在于 chunk 查询是基于 offset 和 limit, 而 chunkById 查询数据库基于id 字段。Id字段通常是整型,大部分情况下是自增的。

chunk
select * from posts offset 0 limit 100
select * from posts offset 101 limit 100
chunkById
select * from posts order by id asc limit 100
select * from posts where id > 100 order by id asc limit 100

通常使用 limt 和 offset 较慢,我们应该尽量避免使用。本文 详细解释了使用offset的问题。 chunkById 使用了类型为整型的 id 字段,查询使用where条款,查询会快很多。

什么时候使用chunkById?

如果你的数据库表中使用了主键自增字段。

  1. 只选择你需要的字段
    通常我们会这样从数据库表中查询结果:
$posts = Post::find(1); //When using eloquent
$posts = DB::table('posts')->where('id','=',1)->first(); //When using query builder

以上代码会生成以下查询语句:

select * from posts where id = 1 limit 1

如你使见,查询语句使用了select * .这意味着会从数据表中查询所有字段。

如果我们确实需要所有字段数据,这样做没问题。但是如果我们只需要指定的字段(id, title), 可以只查询这些字段。

$posts = Post::select(['id','title'])->find(1); //When using eloquent
$posts = DB::table('posts')->where('id','=',1)->select(['id','title'])->first(); //When using query builder

以上代码会生成查询语句:

select id,title from posts where id = 1 limit 1
  1. 当你确切需要1个或2个字段的时候,使用pluck

此技巧主要关注的是,从数据库中获取数据后消耗的时间,不会影响真实的查询时间。

查询特定字段,我们通常会这么做:

$posts = Post::select(['title','slug'])->get(); //When using eloquent
$posts = DB::table('posts')->select(['title','slug'])->get(); //When using query builder

当以上代码执行的时候,幕后会做以下动作:

  • 在数据库中执行selet title, slug from post 查询
  • 为每条数据创建一个Post模型对象(对于query Builder而言, 创建PHP标准对象)
  • 用Post模型创建新的collection
  • 返回Collection

现在, 我们可以这样获取结果:

foreach ($posts as $post){
    // $post is a Post model or php standard object
    $post->title;
    $post->slug;

以上方式会有一个额外的开销,即为每行记录创建一个Post模型,之后还会创建一个Collection对象。如果你确实需要Post模型实例而非数据,这样做是最好。

但如果你只需要这两个值,你可以这样:

$posts = Post::pluck('title', 'slug'); //When using eloquent
$posts = DB::table('posts')->pluck('title','slug'); //When using query builder

执行以上代码,幕后会产生以下动作:

在数据库中执行selet title, slug from post 查询
创建一个数组,以slug为数组索引,title为数组值。
返回数组(数组格式: [ slug => title, slug => title ])
现在可以这样获取结果

foreach ($posts as $slug => $title){
    // $title is the title of a post
    // $slug is the slug of a post

如果你只想查询单个字段,你可以:

$posts = Post::pluck('title'); //When using eloquent
$posts = DB::table('posts')->pluck('title'); //When using query builder
foreach ($posts as  $title){
    // $title is the title of a post

以上方面不用为每一行记录创建一个Post对象,因此减少了查询的内存占用和时间消耗。

我只建议使用在新的代码中使用此方法。个人感觉没必要花时间按照此方式跑回去重构之前的代码。除非你已有的代码处理大量的数据或者你有空闲时间。

 
推荐文章