PDO::fetchAll 与 PDO::fetch 循环

Posted

技术标签:

【中文标题】PDO::fetchAll 与 PDO::fetch 循环【英文标题】:PDO::fetchAll vs. PDO::fetch in a loop 【发布时间】:2011-02-15 18:30:21 【问题描述】:

只是一个简单的问题。

在循环中使用 PDO::fetchAll() 和 PDO::fetch() 之间是否存在性能差异(对于大型结果集)?

我正在获取用户定义类的对象,如果这有什么不同的话。

我最初没有受过教育的假设是 fetchAll 可能会更快,因为 PDO 可以在一个语句中执行多个操作,而 mysql_query 只能执行一个。但是我对 PDO 的内部工作原理知之甚少,文档也没有说明这一点,以及 fetchAll() 是否只是一个转储到数组中的 php 端循环。

有什么帮助吗?

【问题讨论】:

我不知道,但我怀疑它对基准测试是微不足道的。 【参考方案1】:

我发现几乎总是正确的关于 PHP 的一件事是,您自己实现的函数几乎总是比 PHP 等效的要慢。这是因为当用 PHP 实现某些东西时,它没有 C 所具有的所有编译时优化(PHP 是用它编写的),而且 PHP 函数调用的开销很高。

【讨论】:

有时不使用 PHP 内置是值得的。比如搜索一个排序好的数组(二分查找ftw)。 我不确定我是否完全理解你的答案,但我必须在所有对象被获取后再次对它们进行一些操作,这无疑需要另一个 foreach 循环。我是否应该坚持一次获取一个对象并在获取每个对象时对其执行操作? @AlReece45 您描述了两个完全不同的功能。我说的是在 PHP 中重新实现排序函数与使用 PHP 的 sort。 @Byron 我打赌您会发现使用 fetchAll() 获取所有结果仍然会更快,但如果您有疑问,可以使用 microtime(TRUE) 对其进行基准测试。 @Reece45 二分查找是对数的;无论是用 C 还是 PHP 编写的,它都应该很快。 OTOH 排序是 O(n log n) - 可以节省更多。 在理论层面上,在 PHP 中实现二进制搜索可能是有意义的,但除非您的 N 非常大(除非您做错了什么,否则大多数情况下这不是真的),最好只需使用 PHP 提供的 O(N)。【参考方案2】:

具有 200k 随机记录的小基准测试。正如预期的那样,fetchAll 方法更快,但需要更多内存。

Result :
fetchAll : 0.35965991020203s, 100249408b
fetch : 0.39197015762329s, 440b

使用的基准代码:

<?php
// First benchmark : speed
$dbh = new PDO('mysql:dbname=testage;dbhost=localhost', 'root', '');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'SELECT * FROM test_table WHERE 1';
$stmt = $dbh->query($sql);
$data = array();
$start_all = microtime(true);
$data = $stmt->fetchAll();
$end_all = microtime(true);

$stmt = $dbh->query($sql);
$data = array();
$start_one = microtime(true);
while($data = $stmt->fetch())
$end_one = microtime(true);

// Second benchmark : memory usage
$stmt = $dbh->query($sql);
$data = array();
$memory_start_all = memory_get_usage();
$data = $stmt->fetchAll();
$memory_end_all = memory_get_usage();

$stmt = $dbh->query($sql);
$data = array();
$memory_end_one = 0;
$memory_start_one = memory_get_usage();
while($data = $stmt->fetch())
  $memory_end_one = max($memory_end_one, memory_get_usage());


echo 'Result : <br/>
fetchAll : ' . ($end_all - $start_all) . 's, ' . ($memory_end_all - $memory_start_all) . 'b<br/>
fetch : ' . ($end_one - $start_one) . 's, ' . ($memory_end_one - $memory_start_one) . 'b<br/>';

【讨论】:

是的,你没有。这是基准测试的目标:第一个是你做一个 fetchAll 然后做数据的工作。第二个,你会取一行,在这一行上做工作,然后取下一行。一个很好的例子是在显示数据表时,是否需要在写入缓冲区之前存储所有数据? 对不起,我不明白为什么人们会说这是一个糟糕的基准。除非您将该数据返回给用户,否则没有理由存储整个数据集......首先这很糟糕,在这种情况下使用分页。如果您需要在数据库中批量修改数据,您应该在数据库中使用脚本或存储过程来执行此操作,例如临时表。 -1。这绝对是一个糟糕的基准。记忆不是真正的记忆。 memory_get_usage(/* true */) 显示 PHP 本身分配的内存。它向您显示 LIBRARY 分配的内存。 Libmysqlclient 和 mysqlnd 正在使用它们的自己的内存。不是 PHP 的内存。 这不取决于您使用buffered queries 还是不使用@bwoebi?由于缓冲是默认设置,我认为这意味着查询结果被发送到 php 的“进程”,因此使用它的内存?至于内存,也许基准测试应该使用单独的脚本和 memory_get_peak_usage(true) @FélixGagnon-Grenier memory_get*usage() 只会显示由 PHP 控制的内存。直接 malloc() 或 mmap() 调用不会受到尊重。当然,无缓冲查询基本上不是从套接字读取的。这意味着结果会被缓存在 mysql 服务器端。但是缓冲的查询存储在客户端......在通过 malloc() 分配的 libmysqlclient 的内存中。 (mysqlnd 将使用 emalloc(),它通过 Zend 内存分配器分配内存)……但是这个基准测试显然是使用 libmysqlclient 完成的。 (因为数字对于 mysqlnd 来说太不现实了。)【参考方案3】:

@Arkh

// $data in this case is an array of rows;

$data = $stmt->fetchAll();


// $data in this case is just one row after each loop;

while($data = $stmt->fetch())


// Try using

$i = 0;

while($data[$i++] = $stmt->fetch())

内存差异应该可以忽略

【讨论】:

@stancu 顶部和底部变体实际上是相同的,使用 fetch() 看到的额外 MEM 很可能是 while() 开销的产物。 fetch() 的重点是一次处理一行,使用 while() 来完成与 fetchAll(PDO::FETCH_NUM) 相同的事情是愚蠢的,因为您放弃了 PDO 中发生的 C 级编译器优化模块。【参考方案4】:

正如 Mihai Stancu 所说,尽管 fetchAll 优于 fetch + while,但几乎没有内存差异。

Result : 
fetchAll : 0.160676956177s, 118539304b
fetch : 0.121752023697s, 118544392b

我在正确运行时得到了上述结果:

$i = 0;
while($data[$i++] = $stmt->fetch())
    //

所以 fetchAll 消耗的内存更少,但是 fetch + while 更快! :)

【讨论】:

更快? 0.16 (fetchAll) 与 0.12 (fetch) 如果结果集大得多,您会发现 PDOStatement::fetch() 和 PDOStatement::fetchALL() 之间存在显着差异。确定什么是“显着更大”将取决于每行的大小。此外,默认情况下,PDOStatement::Fetch()/fetchAll() 使用获取模式 PDO::FETCH_BOTH,它有效地将每行的大小加倍,更改此设置有助于减少大型结果集上的 MEM 使用。 此外,否决票是因为没有为您的基准测试提供任何参考统计数据,这使其存在固有缺陷。 PHP 函数的各种开销以及不解释 MEM 差异的原因。【参考方案5】:

我知道这是一个老话题,但我遇到了同样的问题。在运行了我自己的简单“基准”并阅读了其他人在这里写的内容后,我得出的结论是,这不是一门精确的科学,虽然人们应该努力编写高质量、轻量级的代码,但一开始就浪费太多时间是没有意义的的项目。

我的建议是:通过运行代码(测试版?)一段时间来收集数据,然后开始优化。

在我的简单基准测试(仅测试执行时间)中,两种方式的结果都在 5% 和 50% 之间变化。我在同一个脚本中运行这两个选项,但是当我首先运行 fetch + 时,它比 fetchall 快,反之亦然。 (我知道我应该单独运行它们,然后数百次得到中值和平均值,然后进行比较,但是 - 正如我在开始时所说 - 我得出结论,就我而言,现在开始这样做还为时过早。)

【讨论】:

【参考方案6】:

但是如果您将获取的数据存储在一个数组中,那么内存使用量肯定是相等的吗?

<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
// database to use
define('DB', 'test');
try

   $dbh = new \PDO('mysql:dbname='. DB .';host='. DB_HOST, DB_USER, DB_PASS);   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   $sql = 'SELECT * FROM users WHERE 1';
   $stmt = $dbh->query($sql);
   $data = array();
   $start_all = microtime(true);
   $data = $stmt->fetchAll();
   $end_all = microtime(true);

   $stmt = $dbh->query($sql);
   $data = array();
   $start_one = microtime(true);
   while($data = $stmt->fetch())
   $end_one = microtime(true);

   // Second benchmark : memory usage
   $stmt = $dbh->query($sql);
   $data = array();
   $memory_start_all = memory_get_usage();
   $data = $stmt->fetchAll();
   $memory_end_all = memory_get_usage();

   $stmt = $dbh->query($sql);
   $data = array();
   $memory_end_one = 0;
   $memory_start_one = memory_get_usage();
   while($data[] = $stmt->fetch())
     $memory_end_one = max($memory_end_one, memory_get_usage());
   

   echo 'Result : <br/>
   fetchAll : ' . ($end_all - $start_all) . 's, ' . ($memory_end_all - $memory_start_all) . 'b<br/>
   fetch : ' . ($end_one - $start_one) . 's, ' . ($memory_end_one - $memory_start_one) . 'b<br/>';

catch ( PDOException $e )

   echo $e->getMessage();

?>

Result : 
fetchAll : 2.6941299438477E-5s, 9824b
fetch : 1.5974044799805E-5s, 9824b

【讨论】:

【参考方案7】:

所有衡量“内存占用”的基准实际上都是不正确的,原因很简单。

默认情况下,PDO 确实会将所有内容加载到内存中,并且它不关心您是使用 fetch 还是 fetchAll。 要真正获得无缓冲查询的好处,您应该指示 PDO 使用无缓冲查询:

$db-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

在这种情况下,您会看到脚本的内存占用有巨大差异

【讨论】:

在使用缓冲查询时使用$stmt-&gt;fetch()(默认)和在非缓冲查询中使用$stmt-&gt;fetch()PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 属性设置为false)有什么区别?我看到即使您使用默认缓冲模式,$stmt-&gt;fetch() 也适用于非常大的数据集,而$stmt-&gt;fetchAll() 可能会返回内存限制错误。 $stmt-&gt;fetch() 有点像 unbuffered

以上是关于PDO::fetchAll 与 PDO::fetch 循环的主要内容,如果未能解决你的问题,请参考以下文章

PDO fetchAll() - json_encode在使用JOIN时不起作用

PDO fetchAll 数组到一维

MySQL PDO fetchAll 作为具有整数索引的数组

PDO fetchAll 将键值对分组到 assoc 数组中

PDO fetchAll 返回空数组,但是可以显示count数量?

LDAP 与 MYSQL .. JA-SIG CAS 与 LDAP 与 CAS 与 MySQL