Laravel / Eloquent утечка памяти при повторном получении одной и той же записи
Я пытаюсь написать функцию laravel, которая получает много записей (более 100000) из одной базы данных и помещает их в другую базу данных. Для этого мне нужно запросить мою базу данных и посмотреть, существует ли пользователь уже. Я неоднократно называю этот код:
$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: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();" тогда использование памяти остается стабильным.
У кого-нибудь есть понимание того, почему эта строка будет использовать память, подобную этой, или знаете более разумный способ выполнить то, что я пытаюсь сделать?
Спасибо за ваше время.
2 ответа
Я пересоздал ваш сценарий и прошел через него с помощью отладчика, потому что не мог понять, какие ужасные вещи могут вызвать проблемы с памятью такого типа. Когда я прошел, я наткнулся на это:
// in Illuminate\Database\Connection
$this->queryLog[] = compact('query', 'bindings', 'time');
Кажется, что каждый запрос, который вы запускаете в Laravel, сохраняется в постоянном журнале, который объясняет увеличение использования памяти после каждого запроса. Чуть выше, следующая строка:
if ( ! $this->loggingQueries) return;
Немного больше копания определило, что loggingQueries
свойство установлено в true по умолчанию и может быть изменено с помощью disableQueryLog
метод, так что это означает, если вы вызываете:
DB::connection()->disableQueryLog();
прежде чем вы выполните все свои запросы, вы не увидите увеличения использования памяти; это решило проблему, когда я запустил свой тест на основе вашего примера кода. Когда вы закончите, если вы не хотите влиять на остальную часть приложения, вы можете позвонить
DB::connection()->enableQueryLog();
для сдачи в аренду лесозаготовок.
Я не могу сказать, почему это не освобождает память. Лучше всего следовать код и узнать, как он делает то, что он делает для этого. Или спроси Тейлора.
Что касается других вещей, которые вы можете сделать:
Кэширование запроса Если вы вызываете один и тот же запрос снова и снова и снова, используйте кеш запросов. Это так же просто, как добавление ->remember($time_to_cache)
на ваш запрос.
Заставить СУБД выполнить всю тяжелую работу. В идеале, вы бы просто insert into select
заявление, но это становится волосатым, когда вы пересекаете базы данных. Вместо этого пакетируйте запросы select и insert, чтобы вы меньше обращались к базам данных и создавали меньше объектов. Это значительно увеличивает нагрузку на систему управления базами данных, которая, возможно, более эффективна при выполнении подобных задач.