我正在尝试将数据从postgres数据库插入mysql数据库。我需要导入大约 100000
条记录。但是,我总是出现内存不足的问题。
Out of memory (allocated 1705508864) (tried to allocate 222764 bytes)
我正在使用Laravel 5来执行此操作,这是代码:
// to avoid memory limit or time out issue
ini_set('memory_limit', '-1');
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '0');
set_time_limit(0);
// this speeds up things a bit
DB::disableQueryLog();
$importableModels = [
// array of table names
];
$failedChunks = 0;
foreach ($importableModels as $postGresModel => $mysqlModel) {
$total = $postGresModel::count();
$chunkSize = getChunkSize($total);
// customize chunk size in case of certain tables to avoid too many place holders error
if ($postGresModel === 'ApplicationFormsPostgres') {
$chunkSize = 300;
}
$class = 'App''Models''' . $mysqlModel;
$object = new $class;
// trucate prev data //
Eloquent::unguard();
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
$object->truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
Eloquent::reguard();
$postGresModel::chunk($chunkSize, function ($chunk) use ($postGresModel, $mysqlModel, $failedChunks, $object) {
// make any adjustments
$fixedChunk = $chunk->map(function ($item, $key) use ($postGresModel) {
$appendableAttributes = $postGresModel::APPEND_FIELDS;
$attributes = $item->getAttributes();
// replace null/no values with empty string
foreach ($attributes as $key => $attribute) {
if ($attribute === null) {
$attributes[$key] = '';
}
}
// add customized attributes and values
foreach ($appendableAttributes as $appendField) {
if ($appendField === 'ssn') {
$value = $attributes['number'];
$attributes[$appendField] = substr($value, 0, 4);
} else {
$attributes[$appendField] = '';
}
}
return $attributes;
});
// insert chunk of data in db now
if (!$object->insert($fixedChunk->toArray())) {
$failedChunks++;
}
});
}
当大约80000
行插入而不是在此之前时,就会出现内存问题。
我怀疑集合map
函数或映射函数内的循环有问题。我什至尝试将内存设置和时间限制设置为无限制,但无济于事。可能是我需要使用引用变量或其他东西,但我不确定如何使用。
是否可以在上面的代码中进行任何优化以减少内存使用?
或者如何通过代码有效地将大型 PostgreSQL 数据库中的大数据导入 MySQL?
谁能说出我在这里做错了什么,或者为什么整个记忆都被消耗掉了?
PS:我正在具有4GB ram(Windows 8)的本地开发计算机上执行此操作。PHP 版本: 5.6.16
是的,您可以更改"memory_limit"。 但这只适用于今天,而不是明天,那时您将需要更多的内存。
计划A:
相反,请编写更多的代码... 将数据分块,例如一次 1000 行。 生成包含所有行的单个 INSERT
语句。 在事务中单独执行它。
计划B:
构建所有行的 CSV 文件,然后使用LOAD DATA INFILE
进行批量插入。
在任一计划中,请避免一次将所有行加载到 RAM 中。 PHP 中的标量和数组有很多开销。
当然,你在某处有内存泄漏。我猜在$chunk->map()
或$object->insert($fixedChunk->toArray())
的某个地方.我们只能猜测,因为实现是隐藏的。
但是,我会尽可能多地使用发电机。代码可能如下所示:
function getAllItems() {
$step = 2000;
for ($offset = 0 ;; $offset += $step) {
$q = "SELECT * FROM items_table LIMIT $offset, $step";
if (! $items = Db::fetchAll($q)) {
break;
}
foreach ($items as $i) {
yield $i;
}
}
}
foreach (getAllItems() as $item) {
import_item($item);
}
我敢说,使用生成器,您将能够将几乎任何数量的数据从一个数据库导入到另一个数据库。
当你抓取PostgreSQL数据时,试着LIMIT
返回(http://www.postgresql.org/docs/8.1/static/queries-limit.html)的大小到合理的大小,然后迭代。
例如,假设您一次获取 20000 行,您将执行"选择..等等。。限制 20000 偏移量 0",则下一次迭代将是"选择 ..等等。。限制 20000 偏移量 20000',(偏移量为 20000 * 您的循环计数器)。
处理这些批次,直到没有剩余的行。
1.- 尝试注释数据处理逻辑的内容,以检查内存泄漏是否在此代码中:
$postGresModel::chunk($chunkSize, function ($chunk) use ($postGresModel, $mysqlModel, $failedChunks, $object) {
// make any adjustments
$fixedChunk = $chunk->map(function ($item, $key) use ($postGresModel) {
///Nothing to do
}
}
2.- 如果您会收到相同的错误,则在尝试从查询结果转储所有行时,mysql 驱动程序 (PDO?) 可能会产生内存泄漏,从而缓冲内存中的所有行。
与PostgreSQL无缓冲查询和PHP(游标)一样,您可以使用游标更改postgreSQL获取行的行为:
$curSql = "DECLARE cursor1 CURSOR FOR SELECT * FROM big_table";
$con = new PDO("pgsql:host=dbhost dbname=database", "user", "pass");
$con->beginTransaction(); // cursors require a transaction.
$stmt = $con->prepare($curSql);
$stmt->execute();
$innerStatement = $con->prepare("FETCH 1 FROM cursor1");
while($innerStatement->execute() && $row = $innerStatement->fetch(PDO::FETCH_ASSOC)) {
echo $row['field'];
}
一些建议。
- 在每个循环中实例化一个新的
$object
对象。根据MySqlModel
的实际结构和项目的数量,它肯定可以使用大量内存(也是因为GC尚未工作,请参阅第二个建议)。在每个循环结束时将其设置为 NULL,即
$object = NULL;
- 如果执行时间不是问题,请在每个循环之间插入一点延迟。这允许 PHP 垃圾收集器完成一些工作并释放未使用的资源。
map
将返回集合的新实例。GC 会清理得太晚了。
尝试替换
$chunk = $chunk->map(function...
跟
$newchunk = $chunk->map(function...
当然,插入时也要使用新块,$object->insert($newchunk->toArray())
.您也可以使用transform
而不是map
。
GC 现在应该收集它,但您可以在插入后添加一个unset($newchunk);
以确保。代码倒数第二行中的unset($object);
也不会受到伤害。