Laravel CSV Import не хватает памяти (допустимая память исчерпана) - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть CSV-файл из членов , который я получаю раз в месяц и который содержит ~ 6000 строк.

Я (пытаюсь) перебрать CSV-файл, проверить, существует ли record в таблице members, и, если это так, проверить, совпадают ли эти данные.

Затем вставьте его в таблицу pending (с соответствующим флагом существует ).

Я использую Laravel и League \ CSV для чтения в файлеэто сохраняется в моей папке storage:

class ImportController extends Controller
{
  public function import(Request $request) {

    $readDirectory = 'storage/csv/';
    $filename = $request->name;

    $stream = fopen($readDirectory.$filename, 'r');
    $reader = Reader::createFromStream($stream, 'r')->setHeaderOffset(0);
    $records = (new Statement())->process($reader);

    // Truncate the imported table prior to import
    Imported::truncate(); 

    foreach ($records as $record) {

        $email = $record['email'];

        $recordExists = $this->recordExists($email);

        if($recordExists) {
          // Compare the md5 of the recordArray and the memberArray and skip the record if thit's the same.
          $memberArray = $this->getmemberArray($recordExists);
          $recordArray = $this->getRecordArray($record);

          if($memberArray['hash'] === $recordArray['hash']) { continue; }

          $record['exists'] = TRUE;
          $this->write($record);

          continue;
        }


        else
        {
          $record['exists'] = FALSE;
          $this->write($record);
          Log::debug("missing: ".$record['URN']);

          continue;
        }
      };
    // End Foreach Loop

    return redirect()->route('upload.show');
  }



  public function recordExists($urn){
    $member = Member::where('email', 'LIKE', $email)->first();
    if ($member == null) { return false; }
    return $member;
  }

  public function getmemberArray($member) {
    $memberArray = [
      'email'       =>  $member->email,
      'first_name'  =>  $member->first_name,
      'last_name'   =>  $member->last_name,
      'age_years'   =>  $member->age_years,
      'gender'      =>  $member->gender,
      'address_1'   =>  $member->address_1,
      'address_2'   =>  $member->address_2,
      'address_3'   =>  $member->address_3,
      'town'        =>  $member->town,
      'county'      =>  $member->county,
      'postcode'    =>  $member->postcode,
      'sport_1'     =>  $member->sport_1,
      'sport_2'     =>  $member->sport_2,
    ];
    $memberArray['hash'] = md5(json_encode($memberArray));
    return $memberArray;
  }

  public function getRecordArray($record) {
    $recordArray = [
      'email'       =>  $record['email'], 
      'first_name'  =>  $record['first_name'], 
      'last_name'   =>  $record['last_name'], 
      'age_years'   =>  $record['age_years'], 
      'gender'      =>  $record['gender'],
      'address_1'   =>  $record['address_1'], 
      'address_2'   =>  $record['address_2'], 
      'address_3'   =>  $record['address_3'], 
      'town'        =>  $record['town'], 
      'county'      =>  $record['county'], 
      'postcode'    =>  $record['postcode'], 
      'sport_1'     =>  $record['sport_1'], 
      'sport_2'     =>  $record['sport_2'], 
    ];
    $recordArray['hash'] = md5(json_encode($recordArray));
    return $recordArray;
  }

  public function write($record) {

    $import = [];

    $import['email']      = $record['email'], 
    $import['first_name'] = $record['first_name'], 
    $import['last_name']  = $record['last_name'], 
    $import['age_years']  = $record['age_years'], 
    $import['gender']     = $record['gender'],
    $import['address_1']  = $record['address_1'], 
    $import['address_2']  = $record['address_2'], 
    $import['address_3']  = $record['address_3'], 
    $import['town']       = $record['town'], 
    $import['county']     = $record['county'], 
    $import['postcode']   = $record['postcode'], 
    $import['sport_1']    = $record['sport_1'], 
    $import['sport_2']    = $record['sport_2'], 
    $import['exists']     = $record['exists']

    DB::table('imported')->insert(
      $import
    );

    Log::debug($record['email']);

    return TRUE;
  }
}

Но я продолжаю получать:

Symfony \ Component \ Debug \ Exception \ FatalErrorException (E_UNKNOWN) Allowed memory size of 134217728 bytes exhausted (tried to allocate 181321056 bytes)

Это работает, если я использую намного меньше строк в моемCSV, но это не вариант.

Ранее я писал в БД с использованием eloquent->save(), но изменил его на DB::table()->insert для повышения производительности.

Я уже добавил следующеедля тестирования, но он все еще ломается.

set_time_limit(0);
ini_set('max_execution_time', 100000);
ini_set('memory_limit','512m');

Я что-то упустил?Какая-то утечка памяти где-то?

Я предполагаю, что она хранит запись в памяти каждый раз, так есть ли способ заставить ее забыть после каждой строки?

ТАКЖЕ: Есть ли способ очистить эту память, чтобы я мог отредактировать код и повторить попытку?

Даже если я остановлюсь и перезапущу php artisan serve, он все равно сохранит то же сообщение об ошибке.

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Я понял, что вы используете php artisan serve для запуска вашего сервера.Вы можете попробовать развернуть какую-либо форму реального веб-сервера, так как вы будете использовать его в производственной среде.Вы можете попробовать Apache, поставляется легко в XAMPP для Windows и Linux.

Вы можете проверить онлайн, как установить Apache HTTP Server или Nginx в вашей операционной системе.Они лучше контролируют и используют память, чем сервер php по умолчанию.

0 голосов
/ 27 сентября 2018

Проблема здесь в том, что League\CSV читает весь файл CSV в память, когда вы делаете:

$records = (new Statement())->process($reader);

Вы должны использовать chunk метод Reader, подобный этомучтобы читать только определенное количество строк одновременно:

foreach($reader->chunk(50) as $row) {
    // do whatever
}

Метод chunk возвращает Generator , который вы можете перебирать.Вы можете найти это упомянутое здесь в документации .

РЕДАКТИРОВАТЬ: Я неправильно прочитал документацию и рекомендовал неправильный метод.

Выв основном, просто нужно перебрать саму $reader:

foreach ($reader as $row) {
    print_r($row);
}

Также, если вы используете Mac или если ваш CSV был создан на одном, вам нужно использовать следующее, чтобы иметь возможность успешно читать большие CSVфайлы:

if (!ini_get('auto_detect_line_endings')) {
    ini_set('auto_detect_line_endings', '1');
}

См. эту часть документации.

...