如何使用 PDO 正确地对大型查询进行分页以避免 "out of memory" 错误?
How to paginate a large query correctly with PDO to avoid "out of memory" errors?
我有一个非常大的 table,我想在 PHP 中逐行处理。
这是我尝试过的:
PDO::fetchAll()
立即加载内存中的所有行:内存不足错误。
使用 PDO::fetchAll()
和 LIMIT X, 1000
进行分页(即将大查询分成小查询)
我很惊讶地看到内存使用量持续增长,直到 内存不足(至少到 1Gb)。为什么在查询之间没有释放内存?
我没有全局变量,没有 class/object 属性,分页被封装在一个方法中,因此在处理当前页面后,垃圾收集器应该收集局部变量(从数据库中获取的行) …我也不把行存储在内存中,我把它写到磁盘上。
这种技术的缺点还在于它会导致多个查询而不是一个查询,这在非常大的 table.
上很慢
PDO::fetchAll()
与 buffered queries
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
最终内存使用率保持在非常低的水平 (~13Mb),但过程非常缓慢,因为每一行都意味着对 MySQL 服务器的网络访问。
所以我的问题:
- 为什么分页方法会长出内存?有解决办法吗?
- 有没有办法批量使用缓冲查询? (例如,在内存中缓冲 1000 行以避免每一行的网络往返)
或者您还有其他更好的解决方案吗?
可能我不明白你的问题...但是,我创建了一个简单的脚本,它用 ~ 5,436,226
行(和 19
列)迭代来自 table 的所有值,并将其保存到out文件中。我改用 PostgeSQL MySQL(但我认为这不是问题,您必须只更改 LIMIT 部分)。
<?
ini_set('memory_limit', '100M');
$pdo = new PDO('pgsql:host=localhost;port=5432;dbname=test', 'postgres', 'postgres');
$page = 0;
while ($pdo) {
echo ($page++).PHP_EOL;
$stmt = $pdo->prepare('SELECT * FROM table ORDER BY id LIMIT 100 OFFSET '.($page*100));
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
file_put_contents('/var/www/test/tmp/out.txt', json_encode($rows), FILE_APPEND);
}
输出文件大小约为 1 Gb。
我有一个非常大的 table,我想在 PHP 中逐行处理。
这是我尝试过的:
PDO::fetchAll()
立即加载内存中的所有行:内存不足错误。
使用
PDO::fetchAll()
和LIMIT X, 1000
进行分页(即将大查询分成小查询)我很惊讶地看到内存使用量持续增长,直到 内存不足(至少到 1Gb)。为什么在查询之间没有释放内存?
我没有全局变量,没有 class/object 属性,分页被封装在一个方法中,因此在处理当前页面后,垃圾收集器应该收集局部变量(从数据库中获取的行) …我也不把行存储在内存中,我把它写到磁盘上。
这种技术的缺点还在于它会导致多个查询而不是一个查询,这在非常大的 table.
上很慢
PDO::fetchAll()
与 buffered queries$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
最终内存使用率保持在非常低的水平 (~13Mb),但过程非常缓慢,因为每一行都意味着对 MySQL 服务器的网络访问。
所以我的问题:
- 为什么分页方法会长出内存?有解决办法吗?
- 有没有办法批量使用缓冲查询? (例如,在内存中缓冲 1000 行以避免每一行的网络往返)
或者您还有其他更好的解决方案吗?
可能我不明白你的问题...但是,我创建了一个简单的脚本,它用 ~ 5,436,226
行(和 19
列)迭代来自 table 的所有值,并将其保存到out文件中。我改用 PostgeSQL MySQL(但我认为这不是问题,您必须只更改 LIMIT 部分)。
<?
ini_set('memory_limit', '100M');
$pdo = new PDO('pgsql:host=localhost;port=5432;dbname=test', 'postgres', 'postgres');
$page = 0;
while ($pdo) {
echo ($page++).PHP_EOL;
$stmt = $pdo->prepare('SELECT * FROM table ORDER BY id LIMIT 100 OFFSET '.($page*100));
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
file_put_contents('/var/www/test/tmp/out.txt', json_encode($rows), FILE_APPEND);
}
输出文件大小约为 1 Gb。