PDO :: fetchAll与PDO :: fetch在循环中


73

只是一个简单的问题。

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

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

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

有什么帮助吗?


我不知道,但是我怀疑这对于基准测试来说是微不足道的。
蒂莫西

Answers:


79

几乎没有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/>';

62
是的,你没有。基准测试的目标是:第一个是您执行fetchAll,然后对数据进行处理。第二个,您将获取一行,在该行上进行工作,然后获取下一行。一个很好的例子是显示数据表时,是否需要在写入缓冲区之前存储所有数据?
2011年

1
对坏死很抱歉,我不明白为什么人们会说这是一个糟糕的基准。除非您将数据返回给用户,否则没有理由存储整个数据集……这首先很糟糕,在这种情况下请使用分页。如果需要整体修改数据库中的数据,则应在数据库中使用脚本或存储过程(例如临时表)来进行此操作。
Populus

3
-1。这绝对是一个糟糕的基准。内存不是真实的内存。memory_get_usage(/* true */)向您显示PHP本身分配的内存。它不会显示LIBRARY分配的内存。Libmysqlclient和mysqlnd使用它们自己的内存。不是PHP的记忆。
bwoebi 2015年

这是否取决于您是否使用了缓冲查询还是不@bwoebi?由于缓冲是默认设置,所以我认为这意味着查询结果将发送到php的“进程”,从而使用它的内存?至于内存,也许基准应该使用单独的脚本和memory_get_peak_usage(真)
费利克斯·加侬-格雷尼尔

2
@FélixGagnon-Greniermemory_get*usage()仅显示由PHP控制的内存。直接malloc()或mmap()调用将不受此限制。当然,无缓冲的查询基本上不会从套接字读取。这意味着结果将在mysql服务器端进行缓冲。但是缓冲的查询是客户端存储的……是通过malloc()分配的libmysqlclient的内存。(mysqlnd将使用通过Zend内存分配器分配内存的emalloc())…但是,该基准测试显然是通过libmysqlclient完成的。(因为数字对于mysqlnd来说太不现实了。)
bwoebi

12

我发现关于PHP的一件事几乎总是如此,那就是您自己实现的功能几乎总是比同等的PHP慢。这是因为,当在PHP中实现某些功能时,它并没有C所具有的所有编译时优化(PHP被写入其中),并且PHP函数调用的开销很大。


有时值得不使用PHP-builtin。例如搜索排序数组(二进制搜索ftw)。
Reece45'5

3
我不确定我是否完全理解您的答案,但是在提取所有对象后,我确实必须再次对它们进行几次操作,这无疑将需要另一个foreach循环。我是否应该坚持一次获取一个对象并对获取的每个对象执行操作?
Lotus Notes

@ AlReece45您描述了两个完全不同的功能。我说的是重新实现PHP中的sort函数与使用PHP中的sort函数sort。@Byron我敢打赌,您会发现使用fetchAll()获取所有结果的速度仍然会更快,但是microtime(TRUE)如果您有疑问,可以将其作为基准。
肯德尔·霍普金斯

@ Reece45二进制搜索是对数的;无论是用C还是PHP编写都应该很快。排序OTOH是O(n log n)-还有很多节省。
mpen

从理论上讲,用PHP实现二进制搜索可能很有意义,但是除非您的N很大(除非您做错了,否则大多数情况下是不正确的),最好只使用O(N) PHP提供。
肯德尔·霍普金斯

10

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

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

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

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


$stmt->fetch()在使用缓冲查询时(默认)与$stmt->fetch()在无缓冲查询时(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY属性设置为false)一起使用有什么区别?我看到即使您使用默认的缓冲模式,它也$stmt->fetch()适用于非常大的数据集,同时$stmt->fetchAll()可能会返回内存限制错误。所以是$stmt->fetch() 有点unbuffered
tonix

9

@阿克

// $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()){}

内存差异应该可以忽略不计


2
@stancu顶部和底部变体实际上是相同的,并且使用fetch()看到的其他MEM可能是while()开销的产物。fetch()的重点是一次处理一行,使用while()完成与fetchAll(PDO :: FETCH_NUM)相同的事情,这是愚蠢的,因为您松开了PDO中发生的C级编译器优化模块。
DavidScherer 2014年

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更快!:)


6
快点?0.16(fetchAll)vs. 0.12(fetch
Joost 2010年

1
使用大得多的结果集,您会发现PDOStatement :: fetch()和PDOStatement :: fetchALL()之间存在显着差异。确定什么是“明显更大”将取决于每一行的大小。此外,默认情况下,PDOStatement :: Fetch()/ fetchAll()使用访存模式PDO :: FETCH_BOTH可以有效地使每一行的大小增加一倍,对此进行更改可以帮助减轻大型结果集上MEM的使用。
DavidScherer 2014年

另外,不赞成投票是因为没有为您的基准提供任何参考统计数据,从而使其固有地存在缺陷。PHP函数的各种开销以及哪些不会导致MEM差异。
DavidScherer 2014年

4

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

<?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

2

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

我的建议是:通过运行代码一段时间(在beta中)来收集数据,然后开始进行优化。

在我的简单基准测试中(仅测试了执行时间),我得到的结果都在5%和50%之间。我在同一脚本中运行两个选项,但是当我第一次运行fetch +时,它比fetchall更快,反之亦然。(我知道我应该单次运行它们,并且获得中位数和均值然后进行比较几百次,但是-正如我在开始时所说的那样-我得出的结论是,现在开始这样做还为时过早。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.