7

次のような基本的なコードセットがあります (コントローラー内):

$sql = 'select * from someLargeTable limit 1000';
$em = $this->getDoctrine()->getManager();
$conn = $em->getConnection();
$statement = $conn->prepare($sql);
$statement->execute();

私の難点は、結果セットが数レコードしかない場合、メモリ使用量がそれほど悪くないことです。$statement->execute(); を実行する前後にデバッグ情報をエコーし​​ました。コードの一部であり、私の実装では次のことがわかりました。

pre-execute... rowCount :: 0 memory: 49.614 MB
post-execute... rowCount :: 1000 memory: 50.917 MB

これを 1000 レコードから 10k に移動すると、MB 使用量の差は 13 MB に拡大します

pre-execute... rowCount :: 0 memory: 49.614 MB
post-execute... rowCount :: 10000 memory: 62.521 MB

最終的に、約 50k レコードを取得すると、最大メモリ割り当てに近づきます。

pre-execute... rowCount :: 0 memory: 49.614 MB
post-execute... rowCount :: 50000 memory: 114.096 MB

この実装では、データの CSV を取得できるようにするコントローラー (さらに言えばコマンド) を作成する方法はありません。確かに、50,000 件以上のエントリは多くのように聞こえますが、その理由は疑問ですが、それは問題ではありません。

私の最終的な質問は、DBAL/接続またはDBAL/ステートメントに、実行時にPHPではなくSQL内にデータ全体をバッファリングするように指示することは可能ですか? たとえば、1,000 万行ある場合、最初の 10,000 行のみを PHP に送信するには、@statement->fetch();を使用してそれらを調べます。カーソルが 10k の終わりに到達したら、配列を切り捨てて、DB から次の 10k をフェッチしますか?

4

3 に答える 3

14

私はちょうど同じ問題に遭遇し、可能な解決策を共有したいと思いました. DBAL は PDO ライブラリを使用し、そのPDO::MYSQL_ATTR_USE_BUFFERED_QUERY設定を true に設定している可能性があります。これは、クエリのすべての結果が mysql 側にキャッシュされ、PDO によってメモリにバッファリングされることを意味します$statement->fetchAll()。これを修正するには、PDO::MYSQL_ATTR_USE_BUFFERED_QUERYfalse に設定する必要がありますが、DBAL にはそれを行う方法がありません。その PDO 接続クラスは、それを取得するパブリック メソッドなしで保護されており、PDO でsetAttributeを使用する方法はありません。繋がり。

したがって、そのような状況では、メモリを節約して速度を上げるために、独自の PDO 接続を使用するだけです。次のように、doctrine db パラメーターを使用して簡単にインスタンス化できます。

$dbal_conn = $this->getDoctrine()->getManager()->getConnection();
$params = $dbal_conn->getParams();
$pdo_conn = new \PDO(
  'mysql:dbname='.$dbal_conn->getDatabase().';unix_socket='.$params['unix_socket'],
  $dbal_conn->getUsername(),
  $dbal_conn->getPassword()
);
$pdo_conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

私は UNIX ソケットを使用していますが、IP ホスト アドレスも簡単に使用できます。

于 2014-11-28T09:58:04.333 に答える
11

選択した回答は間違っています。@kroky の回答を正しいものとして選択する必要があります。

問題はBuffer vs Unbuffered Queriesです。

次の理由により、すべてのクエリの動作を変更することはお勧めできません。

完全な結果セットがサーバーからフェッチされない限り、同じ接続を介してそれ以上クエリを送信することはできません。

したがって、必要な場合にのみ使用してください。200k オブジェクトを超える完全な動作例を次に示します。

    $qb = ...->createQueryBuilder('p');

    $this
        ->em
        ->getConnection()
        ->getWrappedConnection()
        ->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

    $query = $qb->getQuery();
    $result = $query->iterate();
    $batchSize = 20;
    $i = 0;
    foreach ($result as $product)
    {
        $i++;

        var_dump($product[0]->getSku());

        if (($i % $batchSize) === 0) {
            $this->em->flush();
            $this->em->clear(); // Detaches all objects from Doctrine!
        }
    }

ほとんどの場合、いくつかの改良が必要です。

于 2016-03-24T13:43:38.117 に答える