关于一个 Laravel 使用 cursor 占用内存问题
背景#
我需要把一张表的所有数据查询出来,然后逐一查询出来进行逻辑处理。
代码如下:
public function handle()
{
EloquentStoredEvent::query()->cursor()->each(function (EloquentStoredEvent $storedEvent){
// 逻辑处理
$this->info(round(memory_get_usage()/1024/1024, 2).'MB');
});
}
在代码中,我使用了 Eloquent 的 cursor, 他的原理就是使用了 PDO 的 execute 方法,将数据库的结果集放到 php 的内存中,然后通过 php 的生成器 一行一行从结果集 fetch 出来数据转换成 orm 对象。这个比起使用 Eloquent 的 get 更能节约内存开销。
问题#
当我执行代码的时候。它会一行行执行
$this->info(round(memory_get_usage()/1024/1024, 2).'MB');
我发现了个问题,假如有 80 万条数据,第一次执行内存占用 962.36MB
,后面会慢慢增加,到最后一次执行内存占用 1901.39MB
。
那如果有一个脚本需要长时间才能处理完成,你开始观察几分钟可能正常,但是看你过了半个小时它就因为内存不够而运行失败。
找到原因#
后面我发现和 PDO::ATTR_EMULATE_PREPARES
有关系,在 Illuminate\Database\Connectors\Connector
默认 PDO::ATTR_EMULATE_PREPARES => false
,当它为 false
一行行 fetch
才会动态增长。为 true
内存就是固定值。
测试代码#
<?php
ini_set('memory_limit', '3024M');
class Db{
public $dbh;
public function __construct()
{
$this->dbh = new PDO('mysql:host=127.0.0.1;port=3306;dbname=test', 'root', '',
[
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => true,
]);
}
public function fetch()
{
$sth = $this->dbh->prepare("select * from `stored_events`");
$sth->setFetchMode(PDO::FETCH_OBJ);
$sth->execute();
while ($record = $sth->fetch()) {
yield $record;
}
}
}
$db = new Db();
var_dump(round(memory_get_usage()/1024/1024, 2).'MB');
foreach ($db->fetch() as $v) {
var_dump(round(memory_get_usage()/1024/1024, 2).'MB');
};
var_dump(round(memory_get_usage()/1024/1024, 2).'MB');
在测试代码中,
当我将 PDO::ATTR_EMULATE_PREPARES
置为 true 时候,foreach 循环打印的是 1910.33MB
。
当我将 PDO::ATTR_EMULATE_PREPARES
置为 false 时候,foreach 循环打印的是从 931.52MB
增长到 1870.55MB
。
有哪位大神对这一块比较了解的,能解答下吗?
推荐文章: