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() 不“缓冲”所有值的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Doctrine2 (Symfony2) 中按案例排序