Symfony2 / Doctrine 使 $statement->execute() 不“缓冲”所有值

Posted

技术标签:

【中文标题】Symfony2 / Doctrine 使 $statement->execute() 不“缓冲”所有值【英文标题】:Symfony2 / Doctrine make $statement->execute() not "buffer" all values 【发布时间】:2014-10-28 22:30:21 【问题描述】:

我有一个像这样的基本代码集(在控制器内):

$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 使用量的差异增长到 13MB

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 数据。当然,50k+ 条目听起来很多,问题是为什么,但这不是问题。

我的最终问题是:是否可以告诉 DBAL/Connection 或 DBAL/Statement 在执行时缓冲 SQL 中的数据而不是整个 php 中的数据。例如,如果我有 1000 万行,那么只将前 10k 行发送到 PHP...让我通过 @statement->fetch(); 和当光标到达 10k 的末尾,截断数组并从数据库中获取下一个 10k?

【问题讨论】:

【参考方案1】:

我刚刚遇到了同样的问题,想分享一个可能的解决方案。很有可能您的 DBAL 使用 PDO 库并将其 PDO::mysql_ATTR_USE_BUFFERED_QUERY 设置为 true,这意味着您的查询中的所有结果都缓存在 mysql 端并由 PDO 缓冲到内存中,即使您从未调用 $statement->fetchAll()。要解决这个问题,我们只需将PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 设置为 false,但 DBAL 并没有给我们提供方法——它的 PDO 连接类受到保护,没有公共方法来检索它,它也没有给我们提供使用 @ 的方法PDO 连接上的 987654321@。

因此,在这种情况下,我只是使用自己的 PDO 连接来节省内存并加快速度。您可以像这样使用您的学说数据库参数轻松地实例化一个:

$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 主机地址也可以轻松使用。

【讨论】:

您可以通过对 DBAL Connection 对象 ($this->getDoctrine()->getManager()->getConnection()->getWrappedConnection()) 调用 getWrappedConnection 从 DBAL 获取 PDO 连接。 @Markus 你是对的,我不知道为什么我没有使用这种方法,但我在撰写本文时使用的是 Symfony 2.2,并没有真正找到一种简单的方法来获取pdo 连接。【参考方案2】:

选择的答案是错误的,@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!
        
    

它很可能需要一些改进。

【讨论】:

@kroky 的帖子不是对 OP 问题的回答。为什么它应该是被接受的? 而且,您的帖子只是已发布解决方案的扩展,因为它处理无缓冲查询的边缘情况。您可以将此作为对已接受答案的评论发布。 感谢您提供一个实际示例的好答案。存储此参数的当前状态并在执行查询后恢复它不是一个好主意吗? @lxg OP 询问“......在 SQL 中而不是在 PHP 中完整地缓冲数据......”。我认为他的意思是将数据保存在 MySQL 服务器上,而不是将所有内容都泵入 PHP 进程的内存中。如果他在谈论 50k 记录和最大内存分配,这完全有道理。仅仅使用“迭代”的方法并没有太大的作用,它仍然全部被泵入内存(参见上面提到的文章,例如“查询结果立即从 MySQL 服务器传输到 PHP,然后保存在 PHP 进程的内存中) . @EnricoStahn 很公平,我不记得那部分了。在这种情况下,我同意切换到无缓冲查询是有意义的。【参考方案3】:

您可以通过学说配置参数选项禁用查询缓冲区

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        ...
        options:
            1000: false

【讨论】:

以上是关于Symfony2 / Doctrine 使 $statement->execute() 不“缓冲”所有值的主要内容,如果未能解决你的问题,请参考以下文章

Symfony2 和 Doctrine:一对多关系

如何在 Doctrine2 (Symfony2) 中按案例排序

Symfony2 & Doctrine:创建自定义 SQL 查询

Symfony2/Doctrine 提交表单需要很长时间

Symfony2 Doctrine2 获取所有表

Symfony2/Doctrine - 与普通 SQL 相关的实体抽象