为什么 SELECT table 有 200.000 条记录使用了太多内存 (+2GB)?

Why SELECT table with 200.000 records is using too much memory (+2GB)?

我正在从 Oracle 中的 VIEW 中选择数据,实际大小约为 50MB(61 列和 263.000 行)。我只有一列数据长度为 4000,其他所有数据长度最多为 100。

当我选择使用 Laravel(分成 10.000 条记录的包)时,它占用了大约 2.5GB 的内存。

我进行了一些搜索并尝试使用 DB::disableQueryLog、$connection->disableQueryLog() 禁用日志查询,在每个 SELECT 之后包含 gc_collect_cycles 调用并取消设置结果变量 -没有任何影响。


<?php

use Yajra\Oci8\Connectors\OracleConnector;
use Yajra\Oci8\Oci8Connection;

/**
 * @return Oci8Connection
 * @throws \Exception
 */
private function getOracleConnection()
{
    $config = [
        'driver' => 'oracle',
        'host' => 'host',
        'database' => 'database',
        'port' => 'port',
        'username' => 'user',
        'password' => 'password',
        'charset' => 'charset',
        'schema' => 'schema',
        'options' => [
            \PDO::ATTR_PERSISTENT => true
        ],
    ];
    $connector = new OracleConnector();
    $connection = $connector->connect($config);

    $db = new Oci8Connection($connection, $database);

    return $db;
}

protected function loadSourceSystemData(): Collection
{
    $connection = $this->getOracleConnection();

    DB::disableQueryLog();
    $connection->disableQueryLog();

    $package_size = 10000;

    $return = new Collection();
    $offset = 0;

    while(true) {
        $records = $connection->query()
            ->from('ZVPCP003')
            ->take($package_size)
            ->offset($offset)
            ->get();

        if(empty($records)) break;

        $return = $return->merge($records);
        unset($records);

        gc_collect_cycles();
        $offset += $package_size;
    }

    return $return;
}

我的期望是至少使用不到 1GB,虽然很高但可以接受。

更新:我测量了实际内存使用:

Rows: 10000 | Mem: 124 MB  
Rows: 20000 | Mem: 241 MB  
Rows: 30000 | Mem: 357 MB  
Rows: 40000 | Mem: 474 MB  
Rows: 50000 | Mem: 590 MB  
Rows: 60000 | Mem: 707 MB  
Rows: 70000 | Mem: 825 MB  
Rows: 80000 | Mem: 941 MB  
Rows: 90000 | Mem: 1058 MB  
Rows: 100000 | Mem: 1174 MB  
Rows: 110000 | Mem: 1290 MB  
Rows: 120000 | Mem: 1407 MB  
Rows: 130000 | Mem: 1523 MB  
Rows: 140000 | Mem: 1644 MB  
Rows: 150000 | Mem: 1760 MB  
Rows: 160000 | Mem: 1876 MB  
Rows: 170000 | Mem: 1993 MB  
Rows: 180000 | Mem: 2109 MB  
Rows: 190000 | Mem: 2226 MB  
Rows: 200000 | Mem: 2342 MB  
Rows: 210000 | Mem: 2459 MB  
Rows: 220000 | Mem: 2575 MB  
Rows: 230000 | Mem: 2691 MB  
Rows: 240000 | Mem: 2808 MB  
Rows: 250000 | Mem: 2924 MB  
Rows: 260000 | Mem: 3041 MB  
Rows: 263152 | Mem: 3087 MB  

也许通过将 loadSourceSystemData 转换为生成器,然后以块的形式处理数据,这样你一次最多加载 10000 行,因为它们没有收集到一个大集合中,它们可以自动释放。

<?php

function loadSourceSystemData(): iterable
{
    $connection = $this->getOracleConnection();
    DB::disableQueryLog();
    $connection->disableQueryLog();
    $package_size = 10000;
    $offset = 0;
    do {
        $records = $connection->query()->from('ZVPCP003')
                ->take($package_size)
                ->offset($offset)
                ->get();
        $offset += $package_size;
        yield collect($records);
    } while(!empty($records));
}

foreach($this->loadSourceSystemData() as $collection) {
    foreach($collection as $row) {
        // Process row here
    }
}

更新

我尝试从 CSV 文件加载数据以检查开销,使用数组比使用对象占用大约 70% 的内存。

对于像 "P80,A142900,2012,6,35" 这样的 500000 行,数组占用了大约 213MB,而对象数组占用了 136MB。

class Item {
    public $a;
    public $b;
    public $c;
    public $d;
    public $e;
}

if (($handle = fopen("data.csv", "r")) !== FALSE) {
    $row = 0;
    $list = [];
    while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
        $item = new Item();
        $item->a = $data[0];
        $item->b = $data[1];
        $item->c = $data[2];
        $item->d = $data[3];
        $item->e = $data[4];
        $list[] = $item;
        //$list[] = $data;
    }
    fclose($handle);
    $m = memory_get_usage(true) / 1000 / 1000;
    echo "Rows ", count($list), " Memory ", $m, "MB \n";
}

更新 2

如果将数据转换为特定类型,例如,可以节省更多内存。 int,如果某些列有很多重复值,则可以使用缓存。

class Name {
    public $name;
    public function __construct($name)
    {
        $this->name = $name;
    }
}

class NameCache {
    private $cache = [];
    public function getName(string $name) {

        if (isset($this->cache[$name])) {
            return $this->cache[$name];
        }
        $item = new Name($name);
        $this->cache[$name] = $item;
        return $item;
    }
}

$nameCache = new NameCache();
$item = new Item();
$item->a = $nameCache->getName($data[0]);
$item->b = $nameCache->getName($data[1]);
$item->c = (int)$data[2];
$item->d = (int)$data[3];
$item->e = (int)$data[4];

有了这个内存从 136MB 减少到 75MB。