Исчерпание памяти PHP, унаследованный код вызывает ошибку при работе с большими файлами, очищать ли память, выполнять пакетную обработку или увеличивать выделение памяти? - PullRequest
0 голосов
/ 11 июня 2019

Uploader работал нормально, пока файл не стал больше, чем 100 000 строк.Я не писал код, но хочу это исправить.Я работал с другими языками, но не с PHP.Я знаю, что есть разные способы решения этой проблемы, но я не уверен в том, что стоит потратить больше времени.В идеале я хотел бы, чтобы загрузчик принимал файлы любого размера.Изменение распределения памяти кажется самым быстрым решением, но я ожидаю долгосрочных проблем, когда файл перерастет память.Очистка памяти и пакетная загрузка - это две стороны одной монеты, однако в настоящее время загрузчик будет обрабатывать только один файл и одну загрузку в базу данных, каждый раз, когда файл загружается, он удаляет предыдущие данные и заменяет их наданные из файла.В частности, я настраивал загрузчик CSV, а не загрузчик XLSX.

Я уже безуспешно пытался выделить дополнительную память для программы, но она вызвала сбой сервера, и я предпочел бы не делать этого снова.Я также пытался пакетировать файл CSV, но он также не удалось.

<?php 
class Part {
            public $id;
            public $oem;
            public $part_number;
            public $desc;

            // Assigning the values
            public function __construct($id, $oem, $part_number, $desc) {
                $this->id = $id;
                $this->oem = $oem;
                $this->part_number = $part_number;
                $this->desc = $desc;
            }
}
//imports single csv file and returns an array of Parts
function importCSVpartfinder($filename, $brand, $root){ //$filename is a dataTable of dimensions: first row contains dimension labels, second row are units, the first column is the part number
    $handle = fopen($filename, 'r') or die('unable to open file: $filename');
    $contents = fread($handle, filesize($filename));
    fclose($handle);
    $row = explode("\r" , $contents);
    $data = array();
    $data2 = array();
    for ($i=0; $i < sizeof($row); $i++) { 
        $columns = explode(",", $row[$i]);
        array_push($data, $columns);
        }
    $all = array(); //array of all Parts

//I should probably sanatize here

    for ($i=0; $i < sizeof($data); $i++) { 
        if (sizeof($data[$i]) != 1){
            $id = $data[$i][0];
            $oem = $data[$i][1];
            $part_number = $data[$i][2];
            $desc = $data[$i][3];
            $obj = new Part($id, $oem, $part_number, $desc);
            array_push($all, $obj);
        }
    }
    return $all;
}

//returns a message with # of succes and list of failures  //this is slow with large uploads
function addPartsToDB($data, $connection){      //$data is an array of Parts
    //delete
    $deleteSQL = "DELETE FROM Part_finder WHERE 1";
    $res = $connection->query($deleteSQL);
    if (!$res){
        echo " Failed to delete Part_finder data, ";
        exit;
    }
    //insert
    $e=0;
    $s=0;
    $failures = "";
    $d="";
    for ($i=0; $i < sizeof($data); $i++) { 
        $d .= "(".$data[$i]->id.",'".$data[$i]->oem."','".$data[$i]->part_number."','".$data[$i]->desc."'),";
        $s++;

    }
    $d = substr($d, 0, -1);
    $sqlquery = "INSERT INTO Part_finder (id_part, oem, part_number, description) VALUES $d";
    $res = $connection->query($sqlquery);
    if (!$res){
        $sqlError = $connection->error;
        return ( $s." items failed to update. Database error. ".$sqlError);
    }else{
        return ( $s." items updated."); 
    }

/*
    for ($i=0; $i < sizeof($data); $i++) { 
        $d = "(".$data[$i]->id.",'".$data[$i]->oem."','".$data[$i]->part_number."','".$data[$i]->desc."')";
        $sqlquery = "INSERT INTO Part_finder (id_part, oem, part_number, description) VALUES $d";
        #$res = $connection->query($sqlquery);
        if (!$res){
            $failures .= $data[$i]->part_number . "
" ;
            $e++;
        }else{
            $s++;   
        }
    }*/
    #return $sqlquery;

}

function importXLSXpartfinder($filename, $root){
    require($root.'./plugins/XLSXReader/XLSXReader.php');
    $xlsx = new XLSXReader($filename);
/*  $sheetNames = $xlsx->getSheetNames();
    foreach ($sheetNames as $Name) {
        $sheetName = $Name;
    }*/
    $sheet = $xlsx->getSheet("Sheet1");
    $rawData = $sheet->getData();
    #$columnTitles = array_shift($rawData);
    $all = array(); //array of all Parts
    for ($i=0; $i < sizeof($rawData); $i++) { 
        if (sizeof($rawData[$i]) != 1){
            $id = $rawData[$i][0];
            $oem = $rawData[$i][1];
            $part_number = $rawData[$i][2];
            $desc = $rawData[$i][3];
            $obj = new Part($id, $oem, $part_number, $desc);
            array_push($all, $obj);
        }
    }
    return $all;
}

$filename = $file["partfinder"]["tmp_name"];
if($file["partfinder"]["size"] > 100000000){
    echo "File too big".$file["partfinder"]["size"];
    exit;
}
//$file comes from edit.php
if($file["partfinder"]["type"] === "text/csv"   ) {
    $a = importCSVpartfinder($filename, $brand, $root);
}elseif ($file["partfinder"]["type"] === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) {
    $a = importXLSXpartfinder($filename, $root);
}else{
    var_dump($file["partfinder"]["type"]);
    echo ".xlsx or .csv file types only";
    exit;   
}
$b = addPartsToDB($a,$connection);
echo $b;

?>

В настоящее время происходит исчерпание памяти в строке 25

$columns = explode(",", $row[$i]);

, а код ошибки

Fatal error: Allowed memory size of 94371840 bytes exhausted (tried to allocate 20480 bytes) in /www/tools/import-csv-partfinder.php on line 25

В идеале я все же хотел бы загрузить один файл для обновления базы данных, и мне нужно было бы изменить дополнительные программы, чтобы можно было загружать несколько файлов или чтобы база данных не стиралась сама при каждой загрузке.К сожалению, я не могу связаться с человеком, который изначально писал программы, поэтому я сам по себе, чтобы выяснить это.

1 Ответ

1 голос
/ 11 июня 2019

Я бы предложил использовать генератор для чтения вашего CSV, а не для чтения всего объекта в массив (на самом деле два массива в том виде, в каком они написаны в данный момент).Таким образом, вы сохраняете в памяти только одну строку за раз.

function importCSVpartfinder($filename = '') {
    $handle = fopen($filename, 'r');
    while (($row = fgetcsv($handle)) !== false) {
        yield $row;
    }
    fclose($handle);
}

Затем для функции вставки в базу данных используйте подготовленный оператор и выполняйте итерацию генератора, выполняя оператор для каждой строки в файле.

function addPartsToDB($parts, $connection) {
    $connection->query('DELETE FROM Part_finder');
    $statement = $connection->prepare('INSERT INTO Part_finder
                                       (id_part, oem, part_number, description)
                                       VALUES (?, ?, ?, ?)');
    foreach ($parts as $part) {
        $statement->execute($part);
    }
}

Эти примеры упрощены, чтобы показать концепцию.Вы должны быть в состоянии адаптировать их к вашим точным потребностям, но они работают как написано.

addPartsToDB(importCSVpartfinder($filename), $connection);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...