web-dev-qa-db-ja.com

大きな結果セットを伴うPDO / MySQLメモリ消費

約30,000行のテーブルから選択するのに奇妙な時間を過ごしています。

私のスクリプトは、クエリ結果をウォークオーバーするだけの単純なものに、途方もない量のメモリを使用しているようです。

この例は、実際のコードとほとんど類似しておらず、単純なデータベース集約に置き換えることができない、やや工夫された、絶対的な最低限の例であることに注意してください。これは、各行を各反復で保持する必要がないという点を説明することを目的としています。

<?php
$pdo = new PDO('mysql:Host=127.0.0.1', 'foo', 'bar', array(
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
));
$stmt = $pdo->prepare('SELECT * FROM round');
$stmt->execute();

function do_stuff($row) {}

$c = 0;
while ($row = $stmt->fetch()) {
    // do something with the object that doesn't involve keeping 
    // it around and can't be done in SQL
    do_stuff($row);
    $row = null;
    ++$c;
}

var_dump($c);
var_dump(memory_get_usage());
var_dump(memory_get_peak_usage());

この出力:

int(39508)
int(43005064)
int(43018120)

一度にデータを保持する必要がほとんどないのに、なぜ40メガのメモリが使用されるのかわかりません。 「SELECT *」を「SELECThome、away」に置き換えることで、メモリを約6分の1に削減できることはすでにわかっていますが、この使用量でもめちゃくちゃ高く、テーブルは大きくなるだけだと思います。

不足している設定はありますか、それともPDOに注意すべき制限がありますか?これをサポートできない場合は、mysqliを優先してPDOを削除できてうれしいので、それが私の唯一のオプションである場合、代わりにmysqliを使用してこれを実行するにはどうすればよいですか?

23
Shabbyrobe

接続を作成した後、設定する必要があります PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 偽:

<?php
$pdo = new PDO('mysql:Host=127.0.0.1', 'foo', 'bar', array(
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
));
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

// snip

var_dump(memory_get_usage());
var_dump(memory_get_peak_usage());

この出力:

int(39508)
int(653920)
int(668136)

結果のサイズに関係なく、メモリ使用量はほとんど静的なままです。

59
Shabbyrobe

結果セット全体(30,000行すべて)は、表示を開始する前にメモリにバッファリングされます。

データベースに集計を行わせ、必要な2つの数値のみをデータベースに要求する必要があります。

SELECT SUM(home) AS home, SUM(away) AS away, COUNT(*) AS c FROM round
1
Dan Grossman

状況の現実は、すべての行をフェッチし、PHPでそれらすべてを一度に反復できると期待する場合、それらはメモリに存在するということです。

SQLを利用した式と集計を使用することが解決策であると本当に思わない場合は、データ処理を制限/チャンクすることを検討できます。すべての行を一度にフェッチする代わりに、次のようにします。

1)  Fetch 5,000 rows
2)  Aggregate/Calculate intermediary results
3)  unset variables to free memory
4)  Back to step 1 (fetch next set of rows)

ただのアイデア...

1
Jake

別のオプションは、次のようなことを行うことです。

$i = $c = 0;
$query = 'SELECT home, away FROM round LIMIT 2048 OFFSET %u;';

while ($c += count($rows = codeThatFetches(sprintf($query, $i++ * 2048))) > 0)
{
    foreach ($rows as $row)
    {
        do_stuff($row);
    }
}
1
Alix Axel

これまでPHPでこれを行ったことはありませんが、スクロール可能なカーソルを使用して行をフェッチすることを検討してください。例については、 フェッチドキュメント を参照してください。

クエリのすべての結果を一度にPHPスクリプトに返す代わりに、サーバー側で結果を保持し、カーソルを使用して、一度に1つずつ取得する結果を繰り返し処理します。

私はこれをテストしていませんが、サーバーリソースの使用量が増えたり、サーバーとの通信が増えるためにパフォーマンスが低下したりするなど、他の欠点もあるはずです。

フェッチスタイルを変更すると、デフォルトで連想配列と、メモリ使用量を増やすためにバインドされた数値インデックス付き配列の両方が格納されることがドキュメントに示されているため、影響が生じる可能性があります。

他の人が示唆しているように、最初に結果の数を減らすことは、可能であれば、おそらくより良いオプションです。

1
Jarod Elliott