Добавление большого массива данных в таблицу

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

Здравствуйте. Есть задача реализовать возможность обновления каталога товаров на сайте из файла json, который выгружается из 1с.
Возникла сложность на последнем этапе - заполнение таблицы значений свойств товаров. Таблица значений свойств товаров это промежуточная таблица товаров и свойств, которая к тому же еще помимо id товара и id свойства содержит три поля со значениями (строка, число и булево). Скрипт выполняется ровно 10 минут на этом этапе и вылетает. До этого отработал без проблем 20 минут при заполнении таблицы товаров.
Подскажите, как можно оптимизировать данный алгоритм?

Код: Выделить всё

 public function pushProductsProperties($products_properties = [])
    {
        foreach($products_properties as $key => $value){
            
            $product = Products::find()->select(['id'])->where(['code' => $value['Номенклатура']])->one();
            if(!$product){
                continue;
            }
            
            foreach($value['ЗначенияСвойств'] as $k => $v){
                
                $property = Properties::find()->select(['id', 'type'])->where(['code' => $v['Свойство']])->one();
         
                $product_property = ProductsProperties::findOne(['product_id' => $product->id, 'property_id' => $property->id]);
                if(!$product_property){
                    $product_property = new ProductsProperties();
                }
                
                $product_property->product_id = $product->id;
                $product_property->property_id = $property->id;
                if($property->type == 1){
                    $product_property->body = $v['Значение'];
                }elseif($property->type == 2){
                    $product_property->number = $v['Значение'];
                }elseif($property->type == 3){
                    $product_property->bool = $v['Значение'];
                }
                
                if($product_property->save()){
                    unset($product_property);
                    continue;
                }else{
                    return 'Ошибка сохранения свойства товара - '.$v['Свойство'].', товар - '.$value['Номенклатура']; 
                }
            }
        }
        return 'Операция успешно выполнена'; 
    }
элемент массива json, подмассива "значения свойств":

Код: Выделить всё

{
			"Номенклатура": "НФ-00021868",
			"ЗначенияСвойств": [
				{
					"Свойство": "8f9467c3-7dc6-11e7-b977-ec086b039f00",
					"Значение": "РСВ-53054"
				},
				{
					"Свойство": "9165dc09-8959-11e7-b977-ec086b039f00",
					"Значение": "TARKETT"
				},
				{
					"Свойство": "d85ac254-8cb9-11e7-b977-ec086b039f00",
					"Значение": "линолеум"
				},
				{
					"Свойство": "d85ac255-8cb9-11e7-b977-ec086b039f00",
					"Значение": "Акрон 6"
				},
				{
					"Свойство": "a7847d58-8d5f-11e7-b977-ec086b039f00",
					"Значение": 300
				},
				{
					"Свойство": "a7847d5b-8d5f-11e7-b977-ec086b039f00",
					"Значение": "Общественные и жилые помещения "
				},
				{
					"Свойство": "a7847d5c-8d5f-11e7-b977-ec086b039f00",
					"Значение": "Вспененный ПВХ "
				},
				{
					"Свойство": "a7847d5e-8d5f-11e7-b977-ec086b039f00",
					"Значение": "серый"
				},
				{
					"Свойство": "a7847d5f-8d5f-11e7-b977-ec086b039f00",
					"Значение": "Сербия"
				},
				{
					"Свойство": "d60a9d27-25e4-11e8-87ed-ec086b039f00",
					"Значение": 0.3
				}
			]
		},
someweb
Сообщения: 552
Зарегистрирован: 2017.03.09, 10:12

Re: Добавление большого массива данных в таблицу

Сообщение someweb »

Причина вылета?

Использовать db->createCommand без AR, явно начинать транзакции и коммитить пачками по несколько тысяч записей.

Код: Выделить всё

$update->Yii::$app->db->createCommand('INSERT INTO ... VALUES ... ON DUPLICATE KEY UPDATE ...');
$updatedRowCount = 0;
$transaction = Yii::$app->db->beginTransaction();
try {
    foreach ($importArray as $row) {
        extract($row);
        $update->execute();
        $updatedRowCount++;
        if ($updatedRowCount % 5000 === 0) {
            $transaction->commit();
            $transaction = Yii::$app->db->beginTransaction();
        }
    }
    $transaction->commit();
    return true;
} catch (\Throwable $ex) {
    $transaction->rollBack();
    throw $ex;
}
Чтобы правильно задать вопрос, нужно знать бо́льшую часть ответа. Роберт Шекли.
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

Понял, спасибо. Буду пробовать так. Причину вылета не определил. Просто через определенное время процесс прекращается, хотя set_time_limit(0) указал в экшене, в котором вызывается данный метод. 10 минут и все. И главное этот временной интервал выдерживается только в этом методе. Думал может в json что то не нравится, удалял строки на которых обрывалась операция, бесполезно, дело не в json.
someweb
Сообщения: 552
Зарегистрирован: 2017.03.09, 10:12

Re: Добавление большого массива данных в таблицу

Сообщение someweb »

Так такие действия через консольные команды надо делать. Если необходимо загружать через web - используйте очередь.
Чтобы правильно задать вопрос, нужно знать бо́льшую часть ответа. Роберт Шекли.
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

То есть придется дробить массив на подмассивы и ставить в очередь каждый подмассив?
someweb
Сообщения: 552
Зарегистрирован: 2017.03.09, 10:12

Re: Добавление большого массива данных в таблицу

Сообщение someweb »

Если памяти хватает не надо дробить. Просто загруженный через веб интерфейс файл кидаете в очередь, и импорт выполняется не в контексте веб сервера.
Чтобы правильно задать вопрос, нужно знать бо́льшую часть ответа. Роберт Шекли.
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

Установил yii2-queue
Пытаюсь загрузить товары через консольную команду. Если в json-файле 3-4 товара то операция выполняется, если хотя бы несколько сотен в консоли вываливается ошибка:
Job must be a JobInterface instance instead of false

Метод, отвечающий за загрузку товаров:

Код: Выделить всё

public static function pushProducts($products = [])
    {
        $product = Yii::$app->db->createCommand('INSERT INTO `products`(`title`,`alias`,`visible`,`price`,`created`,`modified`,`articul`,`code`,`category_code`) VALUES (:title, :alias, :visible, :price, :created, :modified, :articul, :code, :category_code) ON DUPLICATE KEY UPDATE `title`=VALUES(`title`), `alias`=VALUES(`alias`), `category_code`=VALUES(`category_code`), `visible`=VALUES(`visible`), `articul`=VALUES(`articul`), `price`=VALUES(`price`), `modified`=VALUES(`modified`)');
        $transaction = Yii::$app->db->beginTransaction();
         try {
            foreach($products as $key => $value){
                if(!$value['Название'] || !$value['Родитель']){
                    continue;
                }
                $product->bindValues([
                    ':title' => $value['Название'],
                    ':alias' => self::translit($value['Название']),
                    ':code' => $value['Код'],
                    ':category_code' => $value['Родитель'],
                    ':visible' => $value['ПубликоватьНаСайте'],
                    ':articul' => $value['Артикул'],
                    ':price' => $value['Цена'],
                    ':created' => date('Y-m-d H:i:s'),
                    ':modified' => date('Y-m-d H:i:s'),
                 ])->execute();
             }
             $transaction->commit();
         }catch (\Throwable $ex) {
            $transaction->rollBack();
            throw $ex;
        }
    }
До этого данный скрипт работал через веб и с 20К товаров
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

Удалось решить только вот так эту проблему:

Код: Выделить всё

$sub_arr = [];
            foreach($products as $key => $value){
                $sub_arr[$key] = $value;
                if($key % 100 === 0){
                    Yii::$app->queue->push(new Json([
                        'products' => $sub_arr,
                   ]));
                   $sub_arr = [];
                }
            }
             Yii::$app->queue->push(new Json([
                'products' => $sub_arr,
             ]));
someweb
Сообщения: 552
Зарегистрирован: 2017.03.09, 10:12

Re: Добавление большого массива данных в таблицу

Сообщение someweb »

Вы зачем в очередь отправляете json? Сохраните json в файл и пуште имя файла, тогда должно быть все нормально.
Чтобы правильно задать вопрос, нужно знать бо́льшую часть ответа. Роберт Шекли.
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

В очередь то не json отправляется а массив

Код: Выделить всё

 public function actionIndex()
    {
        $info = Info::findOne(1);
        $arr = [];
        set_time_limit(0);
        
        if($info->json){
            $this->link = Yii::getAlias('@uploads').'/json/'.$info->json; // получаю ссылку на уже загруженный файл json
            $this->file_content = file_get_contents($this->link);   // Получаю содержимое файла
        }
        if(Yii::$app->request->isAjax){
            $arr = json_decode($this->file_content, true);  // Декодирую строку json в массив
            
            $groups = $arr['Группы'];
            $products = $arr['Товары'];
            $properties = $arr['Свойства'];
            $products_properties = $arr['ЗначенияСвойств'];
            
            self::queueProducts($products);
             
	}
        
        return $this->renderAjax('index'));
    }
    
    public static function queueProducts($products)
    {
        $sub_arr = [];
        foreach($products as $key => $value){
            $sub_arr[$key] = $value;
            if($key % 100 === 0){
                Yii::$app->products->push(new JsonProducts([
                    'products' => $sub_arr,
               ]));
               $sub_arr = [];
            }
        }
         Yii::$app->products->push(new JsonProducts([
            'products' => $sub_arr,
         ]));
    }
Так то вроде сейчас нормально все отрабатывает. Не совсем понял о чем вы выше сказали про то, чтобы кидать в очередь имя файла. Можете перефразировать?) И еще что то не могу сообразить как запустить выполнение из скрипта, а не из консоли.
someweb
Сообщения: 552
Зарегистрирован: 2017.03.09, 10:12

Re: Добавление большого массива данных в таблицу

Сообщение someweb »

Вы эту очередь используете?
https://github.com/yiisoft/yii2-queue
Обработку очереди надо из крона дергать или демоном - в документации описано.

Код: Выделить всё

public function actionIndex()
{
    $info = Info::findOne(1);
    if (Yii::$app->request->isAjax) {
        if ($info->json) {
            Yii::$app->queue->push(new JsonProducts([
                'importFile' => Yii::getAlias('@uploads/json/') . $info->json
            ]));
        }
        return $this->renderAjax('index');
    }
    ....
}

.....

class JsonProducts  extends \yii\base\BaseObject implements \yii\queue\JobInterface
{
    public $importFile = '';

    public function execute($queue)
    {
        if (!$this->importFile)  return;
        //Здесь весь код импорта файла $this->importFile
    }

}
Чтобы правильно задать вопрос, нужно знать бо́льшую часть ответа. Роберт Шекли.
Vadim7423
Сообщения: 59
Зарегистрирован: 2016.07.07, 20:21

Re: Добавление большого массива данных в таблицу

Сообщение Vadim7423 »

Да, я Yii2 Queue Extension установил. Спасибо за подсказки
Ответить