@pavlikmd

Как импортировать большие файлы xlsx частями?

Здравствуйте есть код который импортуирет файлы xlsx в базу MySQL.

<?php

session_save_path(dirname($_SERVER['DOCUMENT_ROOT']).'/public_html/tmp');

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('memory_limit', '-1');
set_time_limit('0');




require_once __DIR__ . "/../../vendor/autoload.php";
require_once __DIR__ . "/../../component/Db.php";

$db = Db::connectDB();


$file_url         = "/admin/files/prices/prices_1529587320.xlsx";
$what_row = "2";
$col_article      = "2";
$col_articleSearch     = "0";
$brand            = "Volkswagen";
$col_description        = "3";
$col_group_price        = "5";
$col_price       = "4";
$col_min_value        = "6";
$col_exchange        = "7";
$date             = "2018-01-04";


// чистим на пустоту
foreach(['file_url', 'what_row', 'col_article', 'col_articleSearch', 'brand', 'col_description', 'col_group_price', 'col_price', 'col_min_value', 'col_exchange', 'date'] as $field) {
	$$field = trim($$field);
}

// переводим в INT
foreach(['what_row', 'col_article', 'col_description', 'col_group_price', 'col_price', 'col_min_value', 'col_exchange'] as $field) {
	$$field = (int) $$field;
	if($$field === 0) $$field = '';
}


$filePath = realpath(__DIR__ . "/../../" . $file_url);

$row = $what_row;

/*
* Обновляем статус всех прайсов на old
*/
$sql = "UPDATE `dk_prices` SET status = 'old', price = '0' WHERE brand = '$brand'";
$db->query($sql);


$reader = PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);


foreach ($reader->getWorksheetIterator() as $line) {
	$highestRow = $line->getHighestRow();
	for($row; $row <= $highestRow; $row++) {

		$article = $line->getCellByColumnAndRow( $col_article, $row )->getValue();
		if($col_articleSearch == "0") {
			$searchArticle = str_replace(' ', '', $article);
		} else {
			$searchArticle = $line->getCellByColumnAndRow( $col_articleSearch, $row )->getValue();
		}
		$description   = $line->getCellByColumnAndRow( $col_description, $row )->getValue();
		$group_price   = $line->getCellByColumnAndRow( $col_group_price, $row )->getValue();
		$price   = $line->getCellByColumnAndRow( $col_price, $row )->getValue();
		$min_value   = $line->getCellByColumnAndRow( $col_min_value , $row )->getValue();
		$exchange   = $line->getCellByColumnAndRow( $col_exchange, $row )->getValue();


		// проверяем на пустоту
		foreach(['article', 'searchArticle', 'brand', 'description', 'group_price', 'price', 'min_value', 'exchange'] as $field) {
			if(is_null( $$field )) {
				$$field = "";
			}
		}

		/*
		 * Если такой артикул и бренд существует, то обновляем данные
		 */

		$sql = "SELECT searchArticle and brand from `dk_prices` where searchArticle = ?s and brand = ?s";
		$result = $db->query($sql, $searchArticle, $brand);
		if(mysqli_num_rows($result) > 0) {
			$sql = "UPDATE `dk_prices` SET ?u WHERE searchArticle = ?s and brand = ?s";
			$user_data   = array(
				'description'             => $description,
				'group_price'             => $group_price,
				'price'             => $price,
				'exchange'          => $exchange,
				'min_value'         => $min_value,
				'date'              => $date,
				'status'            => 'update'
			);
			$db->query($sql, $user_data, $searchArticle, $brand);
		} else {
			/*
			 * Если такого бренда и артикула нету, то добавляем нововую строку
			 */
			$sql         = "INSERT INTO `dk_prices` SET ?u";
			$user_data   = array(
				'article'           => $article,
				'searchArticle'     => $searchArticle,
				'brand'             => $brand,
				'description'             => $description,
				'group_price'             => $group_price,
				'price'             => $price,
				'exchange'          => $exchange,
				'min_value'         => $min_value,
				'date'              => $date,
				'status'            => 'new'
			);
			$db->query($sql, $user_data);
		}
	}
}


Но когда файл большой около 60мб тоесть с сотнями тысяч строк, то этот код долго думает а потом пишет Killed, писал в сапорт сказали что он занимает много места.
5b34c2252cd52813503356.png

На сайте бибилотеки здесь есть код для загрузки по частям

$inputFileType = 'Xls';
$inputFileName = './sampleData/example2.xls';

/**  Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter  */
class ChunkReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter
{
    private $startRow = 0;
    private $endRow   = 0;

    /**  Set the list of rows that we want to read  */
    public function setRows($startRow, $chunkSize) {
        $this->startRow = $startRow;
        $this->endRow   = $startRow + $chunkSize;
    }

    public function readCell($column, $row, $worksheetName = '') {
        //  Only read the heading row, and the configured rows
        if (($row == 1) || ($row >= $this->startRow && $row < $this->endRow)) {
            return true;
        }
        return false;
    }
}

/**  Create a new Reader of the type defined in $inputFileType  **/
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);

/**  Define how many rows we want to read for each "chunk"  **/
$chunkSize = 2048;
/**  Create a new Instance of our Read Filter  **/
$chunkFilter = new ChunkReadFilter();

/**  Tell the Reader that we want to use the Read Filter  **/
$reader->setReadFilter($chunkFilter);

/**  Loop to read our worksheet in "chunk size" blocks  **/
for ($startRow = 2; $startRow <= 65536; $startRow += $chunkSize) {
    /**  Tell the Read Filter which rows we want this iteration  **/
    $chunkFilter->setRows($startRow,$chunkSize);
    /**  Load only the rows that match our filter  **/
    $spreadsheet = $reader->load($inputFileName);
    //    Do some processing here
}


Вопрос как этот класс по частям внедрить в мой код ?

p.s. Писать про wait_timeout не надо, у меня и так стоит 900, суть в оптимизации памяти.

Вспомнил что я убирал код связаный с инсертом и апдейтом,
тоесть после этого:
$reader = PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);

скрипт сразу падал, нужно как-то внедрить этот класс, а не игратся с базой.
  • Вопрос задан
  • 1054 просмотра
Решения вопроса 1
65536
@65536
https://github.com/box/spout вот правильная библиотека, жрет мегабайта 2 независимо от размера файла
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
SagePtr
@SagePtr
Еда - это святое
Ещё несколько оптимизаций:
1) Вынести обработку строки в отдельную функцию, вызывать её в цикле (тогда локальные переменные, созданные там, не будут выживать между вызовами функции, что в некоторых случаях может предотвратить утечку памяти)
2)
$sql = "SELECT searchArticle and brand from `dk_prices` where searchArticle = ?s and brand = ?s";
$result = $db->query($sql, $searchArticle, $brand);
if(mysqli_num_rows($result) > 0) {

Вы здесь получаете только кол-во строк, гораздо логичнее сразу запрашивать только количество
$sql = "SELECT COUNT(*) from `dk_prices` where searchArticle = ?s and brand = ?s";

3) Заодно не вижу по коду, чтобы $result как-нибудь явно освобождался. Поидее должен, конечно, срабатывать деструктор при присвоении нового указателя на объект, но как устроен ваш класс для работы с БД - неизвестно, может быть там вообще никакого деструктора нет и результаты накапливаются бесконечно, освобождаясь только после окончания работы скрипта.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы