あるデータベースから大量のレコード (100,000 件以上) を取得し、それを別のデータベースに格納する Laravel 関数を作成しようとしています。そのためには、データベースをクエリして、ユーザーがすでに存在するかどうかを確認する必要があります。次のコードを繰り返し呼び出します。
$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();」という行をコメントアウトすると、メモリ使用量は安定したままになります。
この行がなぜこのようにメモリを使用するのか、または私がやろうとしていることを達成するためのよりスマートな方法を知っている人はいますか?
お時間をいただきありがとうございます。
ベストアンサー1
どのような恐ろしいことがこのタイプのメモリ問題を引き起こすのか見当もつかなかったので、スクリプトを再作成し、デバッガーでステップ実行してみました。ステップ実行しているときに、次のことに気付きました。
// in Illuminate\Database\Connection
$this->queryLog[] = compact('query', 'bindings', 'time');
Laravel で実行するすべてのクエリは永続的なログに保存されるようで、クエリごとにメモリ使用量が増加していることがわかります。そのすぐ上に次の行があります。
if ( ! $this->loggingQueries) return;
さらに詳しく調べてみると、このloggingQueries
プロパティはデフォルトで true に設定されており、メソッドを介して変更できることがわかりdisableQueryLog
ました。つまり、次のように呼び出すと、
DB::connection()->disableQueryLog();
すべてのクエリを実行する前に、メモリ使用量が増加するのを確認することはできません。サンプルコードに基づいてテストを実行したときに、問題は解決しました。完了したら、アプリケーションの残りの部分に影響を与えたくない場合は、次のように呼び出すことができます。
DB::connection()->enableQueryLog();
ログ記録を再度有効にします。