我有一个奇怪的时间处理从一张约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执行此操作呢?
答案 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);
}
}