PHP:PDO的高内存使用率
我有一个包含零售商品数据的远程供应商mysql数据库。我创建了一个php脚本,在一个数据集中选择这些数据,对其进行处理,然后将其插入本地数据库中的3个表中 我的脚本按预期工作,但在脚本结束时,内存使用率似乎非常高 脚本最长的部分是运行查询以选择远程数据。此查询包含一些连接,运行和检索大约100000行大约需要190秒 启动脚本和获取远程数据的开销约为35MB的RES内存。远程查询完成后,数据将在大约10秒钟内被本地处理和插入。在这10秒钟内,脚本的内存使用量从~35MB跃升到最后的300MB 对于这个简单的任务,这似乎占用了大量内存。垃圾收集器似乎没有运行 在阅读有关PHP垃圾收集的内容时,我尝试将部分代码包装到函数中。注意到这有助于垃圾收集。我的情况并非如此 我尝试使用PHP:PDO的高内存使用率,php,memory,pdo,Php,Memory,Pdo,我有一个包含零售商品数据的远程供应商mysql数据库。我创建了一个php脚本,在一个数据集中选择这些数据,对其进行处理,然后将其插入本地数据库中的3个表中 我的脚本按预期工作,但在脚本结束时,内存使用率似乎非常高 脚本最长的部分是运行查询以选择远程数据。此查询包含一些连接,运行和检索大约100000行大约需要190秒 启动脚本和获取远程数据的开销约为35MB的RES内存。远程查询完成后,数据将在大约10秒钟内被本地处理和插入。在这10秒钟内,脚本的内存使用量从~35MB跃升到最后的300MB 对
gc\u collect\u cycles()
手动运行垃圾收集,但这并没有产生任何影响(每次运行时它都返回0个周期)。我试图在每次迭代5957项之后运行它
在迭代结束时,我尝试unset()
并将其设置为null
,但这似乎并没有释放内存
我安装了memprof
扩展来查看是什么消耗了大量内存。到目前为止,explode()
和PDOStatement::fetch()
似乎使用最多。似乎每次迭代都没有释放内存。这将如何被释放
注意:在我的脚本中,我将项目的本地处理划分为5957个组,这是由于参数绑定达到了限制。每个项都绑定了11个参数(5957
*11
=65527
;刚好在65535
的限制之下)
当地环境:
Linux 4.4.0-17763-Microsoft #379-Microsoft x86_64 GNU/Linux (DEBIAN WSL)
PHP 7.0.33-0+deb9u3 (cli)
mysqlnd 5.0.12-dev - 20150407
脚本:
<?php
ini_set('memory_limit', '-1');
set_time_limit(0);
$start = time();
// Step size for processing local inserts
$items_per_step = 5957;
// PDO options
$db_options = [
PDO::ATTR_TIMEOUT => 10,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
// Queries
$fetch_remote_query = file_get_contents(__DIR__ . '/sql/fetch_remote.sql');
$item_query = file_get_contents(__DIR__ . '/sql/add_local.sql');
$about_query = file_get_contents(__DIR__ . '/sql/add_about.sql');
$filters_query = file_get_contents(__DIR__ . '/sql/add_filters.sql');
try {
// Connect to databases
$remotedw = new PDO('dsn', 'user', 'pass', $db_options);
$localdw = new PDO('dsn', 'user', 'pass', $db_options);
// Fetch remote
echo 'Fetching items from the Remote database...' . PHP_EOL;;
$items = $remotedw->query($fetch_remote_query);
$item_count = $items->rowCount();
echo "$item_count items fetched and ready for caching" . PHP_EOL;;
// Calculate steps
$steps_required = ceil($item_count / $items_per_step);
echo "Processing items in $steps_required steps" . PHP_EOL;;
// Run steps
for ($steps_taken = 1, $offset = 0; $steps_taken <= $steps_required; $steps_taken++, $offset += $items_per_step) {
// Step length
$length = $steps_taken * $items_per_step > $item_count ? $item_count - $offset : $items_per_step;
// Initial local query parts for the current step
$item_rows = '';
$about_rows = '';
$filter_rows = '';
$item_data = [];
$about_data = [];
$filter_data = [];
// Step through items
for($i = 0; $i < $length; $i++) {
// Fetch next row
$item = $items->fetch();
// Build items
$item_rows .= '(?,?,?,?,?,?,?,?,?,?,?),';
$item_data[] = $item['sku'];
$item_data[] = $item['mfg_number'];
$item_data[] = $item['handling'];
$item_data[] = $item['taxable'];
$item_data[] = $item['price'];
$item_data[] = $item['qty_available'];
$item_data[] = $item['department'];
$item_data[] = $item['class'];
$item_data[] = $item['description'];
$item_data[] = $item['sales_to_date'];
$item_data[] = $item['show_on_web'];
// Build about
foreach (explode('*', $item['about']) as $about_entry) {
if ($about_entry === '') continue;
$about_rows .= '(?,?),';
$about_data[] = $item['sku'];
$about_data[] = $about_entry;
}
// Build filters
if ($item['fineline']) {
$filter_rows .= '(?,?),';
$filter_data[] = $item['sku'];
$filter_data[] = $item['fineline'];
}
}
// Add items
$localdw
->prepare(str_replace('{{rows}}', rtrim($item_rows, ','), $item_query))
->execute($item_data);
// Add about (sometimes items do not have about data, so check if there are rows)
if ($about_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($about_rows, ','), $about_query))
->execute($about_data);
// Add filters (sometimes items do not have filter data, so check if there are rows)
if ($filter_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($filter_rows, ','), $filters_query))
->execute($filter_data);
}
} catch (PDOException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
echo 'Script finished in ' . (time() - $start) . ' seconds' . PHP_EOL;
我认为rowCount()
可能导致它缓冲所有结果,就好像您调用了$items->fetchAll()
当迭代变量是步长的倍数时,使用收集结果并执行批处理查询的while
循环,而不是使用for
循环
$i = 0;
$item_rows = '';
$about_rows = '';
$filter_rows = '';
$item_data = [];
$about_data = [];
$filter_data = [];
while ($item = $items->fetch()) {
$item_rows .= '(?,?,?,?,?,?,?,?,?,?,?),';
$item_data[] = $item['sku'];
$item_data[] = $item['mfg_number'];
$item_data[] = $item['handling'];
$item_data[] = $item['taxable'];
$item_data[] = $item['price'];
$item_data[] = $item['qty_available'];
$item_data[] = $item['department'];
$item_data[] = $item['class'];
$item_data[] = $item['description'];
$item_data[] = $item['sales_to_date'];
$item_data[] = $item['show_on_web'];
// Build about
foreach (explode('*', $item['about']) as $about_entry) {
if ($about_entry === '') continue;
$about_rows .= '(?,?),';
$about_data[] = $item['sku'];
$about_data[] = $about_entry;
}
// Build filters
if ($item['fineline']) {
$filter_rows .= '(?,?),';
$filter_data[] = $item['sku'];
$filter_data[] = $item['fineline'];
}
if (++$i == $items_per_step) {
$localdw
->prepare(str_replace('{{rows}}', rtrim($item_rows, ','), $item_query))
->execute($item_data);
// Add about (sometimes items do not have about data, so check if there are rows)
if ($about_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($about_rows, ','), $about_query))
->execute($about_data);
// Add filters (sometimes items do not have filter data, so check if there are rows)
if ($filter_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($filter_rows, ','), $filters_query))
->execute($filter_data);
$i = 0;
$item_rows = '';
$about_rows = '';
$filter_rows = '';
$item_data = [];
$about_data = [];
$filter_data = [];
}
}
if ($i > 0) {
// process the last batch
$localdw
->prepare(str_replace('{{rows}}', rtrim($item_rows, ','), $item_query))
->execute($item_data);
// Add about (sometimes items do not have about data, so check if there are rows)
if ($about_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($about_rows, ','), $about_query))
->execute($about_data);
// Add filters (sometimes items do not have filter data, so check if there are rows)
if ($filter_rows) $localdw
->prepare(str_replace('{{rows}}', rtrim($filter_rows, ','), $filters_query))
->execute($filter_data);
}
我对此做了更多的研究。查询运行完成后,似乎会缓冲整个结果。我将PDO::MYSQL\u ATTR\u USE\u BUFFERED\u QUERY
设置为false,它似乎只会在程序的整个生命周期中缓慢上升(而不是一次上升)。不过你的方法要干净得多。我实现了它,但是仍然有很多内存爬行。几乎每次运行fetch()
时,它都不会释放获取的结果。