Laravel 5.5填充器非常缓慢

3
我正在尝试向测试数据库中添加大量点击量,以测试我的仪表盘指标显示。因此,我想生成2.5万个点击量,并将它们与数据库中的随机Post相关联,以使数据保持一致。
我最初尝试在HitFactory类中抓取一个随机的Post,但这非常缓慢(如几千个点击需要数小时)。然后我把随机部分移到种子类中,并只进行了一次调用以最小化数据库访问,认为这会显著加快速度。但事实并非如此——创建单个Hit对象仍需要至少5-10秒钟。
我不确定这是如何可能的——是否有我错过的优化?请注意,我不能只生成1-x之间的随机整数并将其用作链接的Post,因为我正在使用UUID样式ID来管理posts表。
以下是运行时间很长的种子程序:
use Illuminate\Database\Seeder;

class HitsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {

        $posts = App\Post::all();

        $i = 0;

        while ($i <= 25000) {
            $post = $posts->random();
            factory(App\Hit::class)->create(
                [
                    'post_key' => $post->post_key,
                    'subject_code' => $post->subject_code,
                    'subject_id' => $post->subject_id,
                ]
            );
            $i++;
        }
    }
}

编辑:我还尝试了在HitsTableSeeder中生成1-500之间的随机整数,并将其用作$posts集合的索引,以完全消除random()调用。 这仍然比我能想象的要慢。

    while ($i <= 25000) {
        $t = rand(0, 500);
        $post = $posts[$t];

是只有我还是你从未在HitsTableSeederwhile循环中递增$i - Nikola Gavric
你说得对,我已经修正了,但它与这个问题无关。 - user101289
将昂贵的创建代码从循环中移出,仅使用循环来形成适当的数组以进行插入。此外,一次插入 25k 行将会产生超载,您应该考虑使用 chunk() 方法将这个巨大的数组分割成较小的部分。 - Tpojka
@Tpojka-- 即使在循环之外,create 仍然需要被调用 25k 次,不是吗? - user101289
这就是为什么你应该使用分块方法,将其分成100行或根据数据量的大小进行分割。你应该进行测试。 - Tpojka
3个回答

14

仅供参考,如果有人在未来遇到类似的问题,可以尝试使用数据库事务来解决MySQL插入速度极慢的问题。在while循环之前手动启动事务,在之后提交事务,在整个组(或者分块)中手动启动和提交事务可以防止MySQL引擎在每次插入时都进行提交(这会增加大量开销,特别是在大规模插入时),从而获得更好的性能表现。我在这样做之后看到了巨大的性能提升,比仅仅分块要好得多,而分块在我的情况下并没有提高性能。类似于这样:

use Illuminate\Database\Seeder;

class HitsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {

        $posts = App\Post::all();

        $i = 0;

        DB::beginTransaction();

        while ($i <= 25000) {
            $post = $posts->random();
            factory(App\Hit::class)->create(
                [
                    'post_key' => $post->post_key,
                    'subject_code' => $post->subject_code,
                    'subject_id' => $post->subject_id,
                ]
            );
        }

        DB::commit();
    }
}

编辑:仅注明,我实际上还没有尝试过使用工厂,只尝试过常规插入(例如使用Model::create())。希望能有所帮助,但我不能100%确定。


1
你忘记增加 $i 的值,这会导致一个无限循环/超时;将代码修改为:$i++; DB::commit(); - Garrett

0

首先尝试创建Hit实例,然后将它们分成大小为$posts->count()的批次:

$posts = App\Post::all();
$n = $posts->toArray();
$m = 25000;
$c = $posts->count();

factory(App\Hit::class, $m)->make()->chunk($c, function ($hits) use ($n) {
    foreach ($hits as $i => $hit) {
        $hit->fill([
            'post_key' => $n[$i]['post_key'],
            'subject_code' => $n[$i]['subject_code'],
            'subject_id' => $n[$i]['subject_id']
        ]);
        $hit->save();
    }
});

这将为您提供对帖子点击记录的均匀分散。如果您想要更随机的权重,可以使用array_random在内部$hits循环中对帖子进行洗牌。


0
$posts = App\Post::orderBy('id')->take(25000)->chunk(250, function ($posts) {
    foreach ($posts as $post) {
        //
    }
});

请确保您不是一次检索所有记录,并将它们分块为每个查询250条以节省内存。但是,请尝试在工厂目录中使用$faker创建此工厂。

<?php

use Faker\Generator as Faker;

$factory->define(App\Post::class, function (Faker $faker) {
    return [
        'post_key' => $faker->name,
        'subject_code' => $faker->address,
        'subject_id' => $faker->numberBetween(1,25000)/* or either make it auto increment or loop on a counter */,
    ];
});

现在在 Seeder 的 run 方法中只需调用:

factory(App\Post::class, 25000)->create();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接