Yii2中JSON数据批量导入MySQL的性能优化实践

心靈之曲
发布: 2025-11-06 12:18:39
原创
188人浏览过

Yii2中JSON数据批量导入MySQL的性能优化实践

本文深入探讨了在yii2框架下从json文件批量导入数据到mysql时遇到的性能瓶颈及优化策略。通过对比activerecord的save()方法与db命令的insert()及batchinsert(),并结合预加载关联数据,显著提升了导入效率。文章提供了详细的代码示例和注意事项,旨在帮助开发者高效处理大规模数据导入任务。

引言:理解批量数据导入的性能瓶颈

在Web应用开发中,尤其是在数据同步或初始化场景下,我们常需要将大量数据从文件(如JSON)导入到数据库。然而,如果不采用正确的策略,这一过程可能会非常耗时。原始代码中,开发者使用Yii2 ActiveRecord的save()方法在循环中逐条插入数据,导致了显著的性能下降。例如,导入数百条记录的时间会从几秒迅速增长到几十秒甚至更长,对于数万甚至百万级别的数据量,这种方法是不可接受的。

save()方法虽然方便,但它为每个模型实例执行了多项操作:实例化模型、数据验证、触发事件回调、以及最终执行一条独立的SQL INSERT 或 UPDATE 语句。当这些操作在循环中针对大量记录重复执行时,其累积的开销(包括PHP层面的处理和与数据库的多次往返通信)将成为严重的性能瓶颈。

考虑以下原始实现的核心逻辑:

foreach ($products as $product) {
    $item = new Product_dub();
    // ... 赋值属性 ...
    $category = Category_dub::findOne(['id_1c_category' => $product->category_id]); // 每次循环查询
    $brand = Brands_dub::findOne(['id_1c_brand' => $product->brand_id]); // 每次循环查询
    // ... 赋值关联ID ...
    if (!$item->save()) { // 每次循环执行一次INSERT
        // ... 错误处理 ...
    }
}
登录后复制

从上述代码可以看出,除了每次循环进行两次数据库查询来获取category和brand之外,最主要的性能消耗在于$item-youjiankuohaophpcnsave()。即使移除了findOne()查询,save()本身的开销依然巨大。

优化策略一:从ActiveRecord save() 到DB命令 insert()

解决save()性能问题的首要步骤是绕过ActiveRecord的全部生命周期,直接使用Yii2的数据库命令执行INSERT操作。Yii2的Yii::$app->db->createCommand()->insert()方法允许我们直接构建并执行SQL插入语句,极大地减少了框架层面的开销。

通过将$item->save()替换为insert()命令,性能得到了显著提升。例如,在测试中,1107行数据导入时间从最初的数分钟缩短到约40秒。

以下是使用insert()命令进行优化的示例:

foreach ($products as $product) {
    Yii::$app->db->createCommand()->insert('product_dub', [
        'id_1c_product' => $product->id,
        // ... 其他属性 ...
        'category_id' => $categoryMap[$product->category_id] ?? '0', // 假设categoryMap已预加载
        'brand_id' => $brandMap[$product->brand_id] ?? 'No brand',   // 假设brandMap已预加载
        // ...
    ])->execute();
}
登录后复制

这种方法虽然仍是循环中逐条插入,但每次循环仅执行一次SQL INSERT 命令,避免了ActiveRecord的验证、事件等额外处理,从而显著提高了效率。

Find JSON Path Online
Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Find JSON Path Online 30
查看详情 Find JSON Path Online

优化策略二:预加载关联数据以减少查询次数

在原始代码中,Category_dub::findOne()和Brands_dub::findOne()在每次循环中都会执行一次数据库查询,以根据外部ID查找内部ID。对于N条记录,这将导致2N次额外的数据库查询,这就是典型的N+1查询问题,进一步拖慢了导入速度。

为了消除这些重复查询,我们应该在导入循环开始之前,一次性地从数据库中加载所有必要的关联数据,并将其存储在内存中的映射(Map)结构中。这样,在循环内部,我们只需进行内存查找,而非数据库查询。

以下是预加载分类和品牌数据的示例:

$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();
}
登录后复制

通过结合insert()命令和预加载关联数据,我们可以看到一个完整的、大幅优化后的导入逻辑。

更进一步的性能提升:使用 batchInsert()

尽管insert()命令比save()快得多,但它仍然是循环中逐条执行SQL语句。对于处理数万甚至百万级别的数据,最佳实践是使用Yii2提供的batchInsert()方法。batchInsert()能够生成一条包含多行数据的INSERT SQL语句,一次性将多条记录发送到数据库,从而显著减少了与数据库的通信次数,进一步提升了性能。

batchInsert()方法的参数包括表名、列名数组和值数组(每个元素代表一行数据)。

public function importProductFile($file, $return = true)
{    
    $products = json_decode($file, true); // 解码为关联数组更方便
    $dubTableName = Product::tableName() . "_dub";
    $start = time();

    if ($this->db->createDuplicateTable(Product::tableName(), $dubTableName)) {
        $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();

        $rows = [];
        foreach ($products as $product) {
            $rows[] = [
                '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,
            ];
        }

        // 批量插入数据
        if (!empty($rows)) {
            Yii::$app->db->createCommand()->batchInsert('product_dub', array_keys($rows[0]), $rows)->execute();
        }
    }

    $finish = time();
    $res = $finish - $start . "sec. ";

    if ($return) {
        echo $res;
        Answer::success();
    }
}
登录后复制

高级优化与注意事项

除了上述代码层面的优化,还有一些其他因素可以影响批量导入

以上就是Yii2中JSON数据批量导入MySQL的性能优化实践的详细内容,更多请关注php中文网其它相关文章!

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号