PHP:PDO占用大量内存

时间:2019-06-20 00:54:06

标签: php memory pdo

我有一个包含零售项目数据的远程供应商mysql数据库。我创建了一个php脚本,以选择一个数据集中的数据,对其进行处理,然后将其插入到3个表中的本地数据库中。

我的脚本可以按预期工作,但是到脚本结尾,内存使用量似乎确实很高。

脚本的最长部分正在运行查询以选择远程数据。该查询包含一些连接,这些连接大约需要190秒才能运行并检索大约100,000行。

启动脚本和获取远程数据的开销约为RES内存35MB。远程查询完成后,将在10秒钟左右处理数据并在本地插入数据。在这10秒钟内,脚本的内存使用量最终从约35MB跃升至300MB。

对于此简单任务,这似乎占用了大量内存。垃圾回收器似乎没有运行。

通读有关PHP的垃圾回收的知识,我试图将我的部分代码包装在函数中。有人指出,这有助于垃圾回收。就我而言,不是。

我尝试使用gc_collect_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;

1 个答案:

答案 0 :(得分:1)

我认为$items->fetchAll()可能导致它缓冲所有结果,就像您叫for一样。

当迭代变量是步长的倍数时,可以使用while循环来收集结果并执行批处理查询,而不是使用$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); } 循环。

{{1}}