为什么 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。
我正在从 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。