具有大结果集的PDO / MySQL内存消耗

时间:2011-08-01 06:59:40

标签: php mysql pdo

我有一个奇怪的时间处理从一张约30,000行的表中选择。

似乎我的脚本使用了大量的内存,因为它只是简单的向前遍历查询结果。

请注意,这个示例是一个有点人为的,绝对最小的例子,它与真实代码几乎没有相似之处,它不能用简单的数据库聚合代替。它旨在说明每次迭代时不需要保留每一行。

<?php
$pdo = new PDO('mysql:host=127.0.0.1', 'foo', 'bar', array(
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
));
$stmt = $pdo->prepare('SELECT * FROM round');
$stmt->execute();

function do_stuff($row) {}

$c = 0;
while ($row = $stmt->fetch()) {
    // do something with the object that doesn't involve keeping 
    // it around and can't be done in SQL
    do_stuff($row);
    $row = null;
    ++$c;
}

var_dump($c);
var_dump(memory_get_usage());
var_dump(memory_get_peak_usage());

输出:

int(39508)
int(43005064)
int(43018120)

我不明白为什么在任何时候都不需要保存任何数据的情况下使用40兆内存。我已经解决了我可以通过用“SELECT home,away”替换“SELECT *”来减少大约6倍的内存,但是我认为即使这种用法非常高,而且表只会变得更大。

我是否缺少一个设置,或者我应该注意PDO的某些限制?我很高兴摆脱PDO而不支持mysqli,如果它不支持这个,那么如果这是我唯一的选择,我将如何使用mysqli执行此操作呢?

5 个答案:

答案 0 :(得分:56)

创建连接后,您需要将PDO::MYSQL_ATTR_USE_BUFFERED_QUERY设置为false:

<?php
$pdo = new PDO('mysql:host=127.0.0.1', 'foo', 'bar', array(
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
));
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

// snip

var_dump(memory_get_usage());
var_dump(memory_get_peak_usage());

输出:

int(39508)
int(653920)
int(668136)

无论结果大小如何,内存使用率都保持不变。

答案 1 :(得分:1)

在开始查看之前,整个结果集(所有30,000行)都被缓冲到内存中。

您应该让数据库进行聚合,只询问它需要的两个数字。

SELECT SUM(home) AS home, SUM(away) AS away, COUNT(*) AS c FROM round

答案 2 :(得分:1)

情况的实际情况是,如果您获取所有行并希望能够在PHP中迭代所有行,那么它们将存在于内存中。

如果您真的不认为使用SQL驱动的表达式和聚合是解决方案,您可以考虑限制/分块数据处理。不要一次获取所有行,而是执行以下操作:

1)  Fetch 5,000 rows
2)  Aggregate/Calculate intermediary results
3)  unset variables to free memory
4)  Back to step 1 (fetch next set of rows)

只是一个想法......

答案 3 :(得分:1)

我之前在PHP中没有这样做,但您可以考虑使用可滚动游标获取行 - 请参阅the fetch documentation以获取示例。

它不是将您查询的所有结果一次性返回给PHP脚本,而是将结果保存在服务器端,并使用游标迭代它们一次获取一个。

虽然我没有对此进行测试,但它必然会有其他缺点,例如利用更多的服务器资源,并且由于与服务器的额外通信,很可能会降低性能。

更改获取样式也可能会产生影响,因为默认情况下,文档表明它将存储关联数组以及数字索引数组,该数组必然会增加内存使用量。

正如其他人所建议的那样,如果可能的话,首先减少结果数量很可能是更好的选择。

答案 4 :(得分:1)

另一种选择是做类似的事情:

$i = $c = 0;
$query = 'SELECT home, away FROM round LIMIT 2048 OFFSET %u;';

while ($c += count($rows = codeThatFetches(sprintf($query, $i++ * 2048))) > 0)
{
    foreach ($rows as $row)
    {
        do_stuff($row);
    }
}