2

I am trying to import data into MySQL from a JSON file.

public function importProductFile($file, $return = true)
    {    
        $products = json_decode($file);
        $dubTableName = Product::tableName() . "_dub";
        $start = time();
        if ($this->db->createDuplicateTable(Product::tableName(), $dubTableName)) {
    
            $i = 0;
    
            foreach ($products as $product) {
                $i++;
                $item = new Product_dub();
                $item->id_1c_product = $product->id;
                $category = Category_dub::findOne(['id_1c_category' => $product->category_id]);
    
                if (!$category) {
                    Answer::failure("В этом товаре отсутствует категория или такой категории не существует: " . $product->title);
                }
    
                $item->category_id = $category->id;
                $item->title = $product->title;
                $brand = Brands_dub::findOne(['id_1c_brand' => $product->brand_id]);
    
                if (!$brand) {
                    Answer::failure("В этом товаре отсутствует бренд/изготовитель: " . $product->title);
                }
    
                $item->brand_id = $brand->id;
                // $item->shortdesc = $product->shortdesc;
                $item->content1 = $product->content1;
                $item->content2 = $product->content2;
                $item->content3 = $product->content3;
                $item->link_order = $product->link_order;
                $item->img = $product->img;
                $item->in_stock = $product->in_stock ? 1 : 0;
                $item->is_popular = $product->is_popular ? 1 : 0;
    
                if (!$item->save()) {
                    Answer::failure("Не удалось импортировать: Проверьте данные в " . $product->title);
                }
    
                if ($i == 200) {
                    break;
                }
            }
        }
    
        $finish = time();
        $res = $finish - $start . "sec. ";
    
        if ($return) {
            echo $res;
            Answer::success();
        }
    }

There are about 1100 objects in my JSON file. It takes 7 seconds to add 100 rows to the database. Adding 200 lines - 15 seconds. 300 = 33 sec, 400 = 58 sec. Why does it slow down over time and how to speed up this process?

I do everything on the local OpenServer server. PHP 7.2 version, Xeon 2620v3 processor, 16 GB DDR4, HDD.

UPD 1.

"Can you try not importing and just determine the speed of reading" - I comment $item->save() and get 1-2 sec for all of JSON files. "In each iteration of your cycle you are running 2 DB queries to load category and brand." - I tried to delete these lines for test - but the result was 1-2 seconds faster than with 2 DB queries.

UPD 2.

I changed save() to insert() - the speed has increased. Now all JSON (1107 lines) is imported in 40 seconds.

Are there faster ways to load ready-made data from JSON into the database? What if there are 100 thousand lines or a million? Is it normal practice to wait a few hours?

public function importProductFile($file, $return = true)
    {    
        $products = json_decode($file);
        $dubTableName = Product::tableName() . "_dub";
        $start = time();

        if ($this->db->createDuplicateTable(Product::tableName(), $dubTableName)) {
            $start = time();
            $categoryMap = Category_dub::find()->select(['id', 'id_1c_category'])->indexBy('id_1c_category')->column();
            $brandMap = Brands_dub::find()->select(['id', 'id_1c_brand'])->indexBy('id_1c_brand')->column();

            foreach ($products as $product) {
                Yii::$app->db->createCommand()->insert('product_dub', [
                    'id_1c_product' => $product->id,
                    'category_id' => $categoryMap[$product->category_id] ?? '0',
                    'title' => $product->title,
                    'brand_id' => $brandMap[$product->brand_id] ?? 'No brand',
                    'content1' => $product->content1,
                    'content2' => $product->content2,
                    'content3' => $product->content3,
                    'link_order' => $product->link_order,
                    'img' => $product->img ?? 'no-image.png',
                    'in_stock' => $product->in_stock ? 1 : 0,
                    'is_popular' => $product->is_popular ? 1 : 0,
                ])->execute();
            }
        }
        }
    
        $finish = time();
        $res = $finish - $start . "sec. ";
    
        if ($return) {
            echo $res;
            Answer::success();
        }
    }
Regolith
  • 2,944
  • 9
  • 33
  • 50
Makinaru
  • 41
  • 5
  • Can you try not importing and just determine the speed of reading the file and creating all of the objects? – Chris Haas Dec 03 '21 at 14:35
  • 1
    In each iteration of your cycle you are running 2 DB queries to load category and brand. What about loading map of categories/brands in query before cycle? Something like this: `$categoryMap = Category_dub::find()->select(['id', 'id_1c_category'])->indexBy('id_1c_category')->column();`. Then inside the cycle you would do `$item->category_id = $categoryMap[$product->category_id];`. Same way for brands. Also, if you need to insert multiple rows at once, it might be better to use [`Command::batchInsert()`](https://www.yiiframework.com/doc/api/2.0/yii-db-command#batchInsert()-detail). – Michal Hynčica Dec 03 '21 at 15:34
  • Where are the INSERT statements? – Rick James Dec 05 '21 at 22:59
  • "Can you try not importing and just determine the speed of reading" - I comment $item->save() and get 1-2 sec for all of JSON file. "In each iteration of your cycle you are running 2 DB queries to load category and brand." - I tried to delete this lines for test - but the result was 1-2 second faster then with 2 DB queries. Now I looking for some info for batchInsert() – Makinaru Dec 06 '21 at 06:52
  • Using https://www.yiiframework.com/doc/api/2.0/yii-db-command#batchInsert()-detail should be faster than inserting rows one by one. Using active record will be noticeable slower, since AR is doing much more work (validation, typcasting) than just simple insert command. – rob006 Dec 07 '21 at 10:28
  • I've done it) Thx! But I used insert() – Makinaru Dec 09 '21 at 16:55

2 Answers2

2

I changed save() to insert() - the speed has increased. Now all JSON (1107 lines) is imported in 40 seconds. Are there faster ways to load ready-made data from JSON into the database? What if there are 100 thousand lines or a million? Is it normal practice to wait a few hours?

public function importProductFile($file, $return = true)
    {    
        $products = json_decode($file);
        $dubTableName = Product::tableName() . "_dub";
        $start = time();

        if ($this->db->createDuplicateTable(Product::tableName(), $dubTableName)) {
            $start = time();
            $categoryMap = Category_dub::find()->select(['id', 'id_1c_category'])->indexBy('id_1c_category')->column();
            $brandMap = Brands_dub::find()->select(['id', 'id_1c_brand'])->indexBy('id_1c_brand')->column();

            foreach ($products as $product) {
                Yii::$app->db->createCommand()->insert('product_dub', [
                    'id_1c_product' => $product->id,
                    'category_id' => $categoryMap[$product->category_id] ?? '0',
                    'title' => $product->title,
                    'brand_id' => $brandMap[$product->brand_id] ?? 'No brand',
                    'content1' => $product->content1,
                    'content2' => $product->content2,
                    'content3' => $product->content3,
                    'link_order' => $product->link_order,
                    'img' => $product->img ?? 'no-image.png',
                    'in_stock' => $product->in_stock ? 1 : 0,
                    'is_popular' => $product->is_popular ? 1 : 0,
                ])->execute();
            }
        }
        }
    
        $finish = time();
        $res = $finish - $start . "sec. ";
    
        if ($return) {
            echo $res;
            Answer::success();
        }
    }
Makinaru
  • 41
  • 5
  • Does this answer your question or are you trying to provide more information? – DarkBee Dec 06 '21 at 07:44
  • This made the recording faster, but I would like to know if there are any other ways to speed up this process. – Makinaru Dec 06 '21 at 10:14
  • An answer should not contain any follow-up question. Update the current question or ask a new one. Anyway, using a transaction should speed up the process – DarkBee Dec 06 '21 at 10:18
1

You can use the bulk insert as mentioned in this answer and Yii2 docs. Using this bulk insert, you need to remember that the event will not be triggered.

Yii::$app->db->createCommand()->batchInsert('product_dub', array_keys(reset($products)), $products)->execute();
elfarqy
  • 161
  • 1
  • 4