You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Moodle 4.1 PHP CLI百万行CSV导出脚本内存持续增长问题排查

解决Moodle CLI脚本导出大数据时的内存增长问题

一、优先优化现有脚本(无需分进程)

你的内存增长问题大概率来自get_records_sql一次性将1万行数据加载为数组,加上Moodle内部对象的状态积累,以下是针对性优化:

  1. 改用数据库记录集(Recordset)替代数组查询
    get_records_sql会把所有查询结果一次性存入数组,内存占用高且容易残留引用。换成get_recordset_sql返回迭代器,逐行处理后立即释放内存:
$limit = 10000;
$rowCount = $table->getRowsCount();
fputcsv($file, $columns,';');

for ($offset = 0; $offset < $rowCount; $offset += $limit) {
    // 使用recordset而非get_records_sql
    $recordset = $DB->get_recordset_sql($sql . " LIMIT $limit OFFSET $offset", $params);
    foreach ($recordset as $row) {
        $formattedRow = array_values($table->format_row($row));
        fputcsv($file, $formattedRow, ';');
        // 立即清理单行数据引用
        unset($row, $formattedRow);
    }
    // 必须关闭recordset释放数据库资源
    $recordset->close();
    echo "\n offset $offset memory: " . (memory_get_usage() / 1024) . "KB\n" ;
    gc_collect_cycles();
}
  1. 清理$table对象的潜在缓存
    如果format_row方法内部存在缓存机制,可尝试在每次分块后重置相关状态。若Moodle API未提供直接的清理方法,可考虑在分块循环内重新初始化$table(注意性能影响,仅在内存泄漏严重时使用):
// 在分块循环内重新初始化table
$table = custom_report_table_view::create(264);
$table->setup();
$table->download = 'excel';
  1. 确保文件操作的资源释放
    虽然你已经关闭了文件,但要确保在循环中没有其他未释放的资源,比如临时变量、数据库连接的隐式引用。

二、分进程方案(上述优化无效时使用)

如果内存泄漏问题无法通过脚本优化解决,可将每个分块作为独立进程执行,进程退出后内存会自动释放:

1. 主脚本(负责调度分块)

<?php
define('CLI_SCRIPT', true);
require "config.php";
use core_reportbuilder\table\custom_report_table_view;

$table = custom_report_table_view::create(264);
$table->setup();
$table->download = 'excel';
$columns = (new table_dataformat_export_format($table, $table->download))->format_data($table->headers);
[$sql, $params] = $table->getPaginatedDataSQL();

$filePath = '/var/excelreport/tmp_264_' . date("d-m-Y_H:i:s") . '.csv';

// 先写入表头
try {
    $file = fopen($filePath, 'w');
    fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
    fputcsv($file, $columns,';');
    fclose($file);
    echo "文件 '$filePath' 已创建并写入表头。\n";
} catch (\Throwable $throwable) {
    echo $throwable->getMessage();
    echo "创建文件 '$filePath' 失败。\n";
    exit(1);
}

$limit = 10000;
$rowCount = $table->getRowsCount();
$totalChunks = ceil($rowCount / $limit);

// 将SQL和参数存入临时文件,供子脚本读取
$tempSqlFile = tempnam(sys_get_temp_dir(), 'moodle_export');
file_put_contents($tempSqlFile, json_encode(['sql' => $sql, 'params' => $params]));

// 逐个调度分块进程
for ($chunkIndex = 0; $chunkIndex < $totalChunks; $chunkIndex++) {
    $offset = $chunkIndex * $limit;
    $command = "php /path/to/export_chunk.php --file '$filePath' --offset $offset --limit $limit --sql-file '$tempSqlFile'";
    echo "正在处理分块 $chunkIndex(偏移量 $offset)...\n";
    exec($command, $output, $returnCode);
    if ($returnCode !== 0) {
        echo "分块 $chunkIndex 处理失败:" . implode("\n", $output) . "\n";
    }
}

// 清理临时文件
unlink($tempSqlFile);
echo "所有分块处理完成。\n";

2. 子脚本(处理单个分块)

<?php
define('CLI_SCRIPT', true);
require "config.php";
use core_reportbuilder\table\custom_report_table_view;

// 解析命令行参数
$options = getopt('', ['file:', 'offset:', 'limit:', 'sql-file:']);
if (!isset($options['file'], $options['offset'], $options['limit'], $options['sql-file'])) {
    echo "缺少必要参数。\n";
    exit(1);
}

$filePath = $options['file'];
$offset = (int)$options['offset'];
$limit = (int)$options['limit'];
$sqlData = json_decode(file_get_contents($options['sql-file']), true);
$sql = $sqlData['sql'];
$params = $sqlData['params'];

// 初始化table(子进程独立运行,需重新初始化)
$table = custom_report_table_view::create(264);
$table->setup();
$table->download = 'excel';

// 追加模式打开文件
$file = fopen($filePath, 'a');
if (!$file) {
    echo "无法打开文件 '$filePath' 进行追加。\n";
    exit(1);
}

// 使用recordset逐行处理
$recordset = $DB->get_recordset_sql($sql . " LIMIT $limit OFFSET $offset", $params);
foreach ($recordset as $row) {
    $formattedRow = array_values($table->format_row($row));
    fputcsv($file, $formattedRow, ';');
    unset($row, $formattedRow);
}
$recordset->close();
fclose($file);

echo "分块偏移量 $offset 处理完成。\n";
exit(0);

内容的提问来源于stack exchange,提问作者Kolt Stevenson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:44:56