19

我正在尝试编写一个 laravel 函数,该函数从一个数据库中获取大量记录(100,000+)并将其放入另一个数据库中。为此,我需要查询我的数据库并查看用户是否已经存在。我反复调用这段代码:

$users = User::where('id', '=', 2)->first();

然后在这种情况发生了几百次之后,我的内存就用完了。所以,我做了一个使用所有可用内存的极简示例,它看起来像这样:

<?php

use Illuminate\Console\Command;

class memoryleak extends Command
{
    protected $name = 'command:memoryleak';
    protected $description = 'Demonstrates memory leak.';

    public function fire()
    {
        ini_set("memory_limit","12M");

        for ($i = 0; $i < 100000; $i++)
        {
            var_dump(memory_get_usage());
            $this->external_function();
        }
    }

    function external_function()
    {
        // Next line causes memory leak - comment out to compare to normal behavior
        $users = User::where('id', '=', 2)->first();

        unset($users);
        // User goes out of scope at the end of this function
    }
}

这个脚本的输出(由 'php artisan command:memoryleak' 执行)看起来像这样:

int(9298696)
int(9299816)
int(9300936)
int(9302048)
int(9303224)
int(9304368)
....
int(10927344)
int(10928432)
int(10929560)
int(10930664)
int(10931752)
int(10932832)
int(10933936)
int(10935072)
int(10936184)
int(10937320)
....
int(12181872)
int(12182992)
int(12184080)
int(12185192)
int(12186312)
int(12187424)
PHP Fatal error:  Allowed memory size of 12582912 bytes exhausted (tried to allocate 89 bytes) in /Volumes/Mac OS/www/test/vendor/laravel/framework/src/Illuminate/Database/Connection.php on line 275

如果我注释掉“$users = User::where('id', '=', 2)->first();”这一行 然后内存使用保持稳定。

有没有人知道为什么这条线会使用这样的内存,或者知道一种更聪明的方法来完成我想要做的事情?

感谢您的时间。

4

2 回答 2

43

我重新创建了您的脚本并使用调试器逐步完成它,因为我无法理解哪种可怕的事情会导致这种类型的内存问题。当我走过时,我遇到了这个:

// in Illuminate\Database\Connection
$this->queryLog[] = compact('query', 'bindings', 'time');

你在 Laravel 中运行的每个查询似乎都存储在一个持久日志中,这解释了每次查询后你的内存使用量增加。在此之上,是以下行:

if ( ! $this->loggingQueries) return;

进一步挖掘确定该loggingQueries属性默认设置为 true,并且可以通过该disableQueryLog方法进行更改,这意味着,如果您调用:

 DB::connection()->disableQueryLog();

在执行所有查询之前,您不会看到内存使用量不断增加;当我根据您的示例代码运行测试时,它解决了问题。完成后,如果您不想影响应用程序的其余部分,您可以调用

DB::connection()->enableQueryLog();

重新启用日志记录。

于 2013-09-13T01:14:05.530 回答
1

我不能说它为什么不释放内存。你最好的选择是遵循代码并了解它是如何为那个代码做的。或者问泰勒。

至于其他你可以做的事情:

缓存查询如果您一遍又一遍地调用相同的查询,那么请使用查询缓存。就像添加->remember($time_to_cache)到您的查询一样简单。

让 DBMS 完成所有艰苦的工作。理想情况下,您只需执行一个insert into select语句,但是当您跨数据库时,这会变得很麻烦。取而代之的是,批处理选择和插入查询,这样您就可以减少对数据库的调用并创建更少的对象。这将更多繁重的工作交给了数据库管理系统,可以说在这些类型的任务中效率更高。

于 2013-09-12T23:12:23.917 回答