REST API 的 Laravel DB 查询非常慢

Posted

技术标签:

【中文标题】REST API 的 Laravel DB 查询非常慢【英文标题】:Laravel DB query for REST API very slow 【发布时间】:2021-04-11 01:57:06 【问题描述】:

我正在使用 Laravel 迈出第一步,并尝试编写一个音乐播放列表。该数据库有 3 个实体/模型:

Play <-n-1-> Song <-n-1-> Artist

表创建:

        Schema::create('plays', function (Blueprint $table) 
            $table->id();
            $table->bigInteger('song_id')->unsigned();
            $table->foreign('song_id')
                ->references('id')
                ->on('songs')
                ->onDelete('cascade');
            $table->dateTimeTz('date');
            $table->bigInteger('station_id')->unsigned();
            $table->foreign('station_id')
                ->references('id')
                ->on('stations');
            $table->timestamps();

            $table->unique(['song_id', 'date']);
        );

        Schema::create('songs', function (Blueprint $table) 
            $table->id();
            $table->bigInteger('artist_id')->unsigned();
            $table->foreign('artist_id')
                ->references('id')
                ->on('artists')
                ->onDelete('cascade');
            $table->string('title');
            $table->string('cover_url')->nullable();
            $table->smallInteger('cover_width')->nullable();
            $table->smallInteger('cover_height')->nullable();
            $table->string('asin')->nullable();
            $table->date('last_cover_check')->nullable();
            $table->timestamps();

            $table->unique(['artist_id', 'title']);
        );

        Schema::create('artists', function (Blueprint $table) 
            $table->id();
            $table->string('name')->unique();
            $table->timestamps();
        );

该数据库有大约 856000 次播放,包含 33000 首歌曲和 15000 位艺术家。

一开始我尝试直接使用模型来加载plays->songs->artist。

return PlayResource::collection(Play::whereDate('date', $date)->get());

使用 Insomnia,通过 Laravel 对 REST API 进行此查询需要 2.6 秒。 我认为这非常慢。

如果我对 Laravel 的理解正确,它会处理很多数据库查询:

剧本查询 另一首歌曲 但是,对于每部戏剧/歌曲,都需要单独查询其艺术家 (?)

所以我尝试创建一个 JOIN 查询来只有一个 DB 查询:

return DB::table('plays')
    ->leftJoin('songs', 'plays.song_id', '=', 'songs.id')
    ->leftJoin('artists', 'songs.artist_id', '=', 'artists.id')
    ->select('plays.*', 'songs.*', 'artists.*')
    ->whereDate('plays.date', $date)
    ->orderByDesc('plays.date')
    ->get();

这个查询有点快,但仍然很慢:2.2s

直接在 DB 上调用的等效 SQL 查询要快得多:

SELECT *
FROM plays p
LEFT JOIN songs s ON p.song_id = s.id
LEFT JOIN artists a ON s.artist_id = a.id
WHERE DATE(p.date) = "2020-12-30"
ORDER BY p.date DESC;

-> 0.4s

是我做错了什么还是这是使用 Laravel 时的典型开销?

EDIT1:

好的,我找到了DB::getQueryLog()(但这不是我想要的)。现在我知道了:使用三个带有链式预加载的表只会导致 3 个数据库查询。我的第一个是最慢的,将近 400 毫秒。这两个查询每个只需要 1-2 毫秒。

我还发现 Lumen 应该比 Laravel 更快。所以我试了一下。我使用模型的第一个查询现在需要 1.4 秒。为输出映射添加 JsonResources 会增加 0.3s -> 1.7s。在我看来,所有这一切都还很慢。

但我认为这是我使用现代框架必须付出的代价。 (我 2006 年的旧播放列表没有使用任何框架)。

EDIT2: 第一个查询可以通过以下方式显着增强:

    date 列上添加索引 并在不使用函数(DATE(`date`)-&gt;whereDate('date', ...))的情况下进行查询,而是使用 -&gt;whereBetween('date', [$date, $date.' 23:59:59']) 现在第一个查询只需要 5 毫秒(从之前的 400 毫秒)。整个 Lumen 查询我是 1.3 秒。

SQL 查询现在只需

EDIT3 我现在确实使用 Rust(actix_web,diesel) 创建了这个完全相同的 REST API,并且使用相同数据库的相同请求需要 16 毫秒。这些 php 框架有多慢真是太疯狂了。我现在将专注于 Rust,并为后端放弃 PHP/Laravel/Lumen。

【问题讨论】:

尝试测量而不转换为特殊集合。只需Play::whereDate('date', $date)-&gt;with(['artist', 'song'])-&gt;get() 让我们看看生成的 SQL,以便我们发现问题。 我在查询后添加了一个强制异常以获取 Laravel 调试屏幕。在“调试”选项卡下,我可以看到之前的查询以及它运行了多长时间。使用 JOIN 查询,生成的查询看起来与我编写的 SQL 查询几乎相同,耗时 0.4 秒。所以开销来自 Laravel。不是来自生成的查询。 【参考方案1】:

您可以通过将以下函数添加到您的 app/providers/AppServiceProvider.php 文件中来检查 Laravel 正在向数据库发起的查询:

public function boot()
        if(env('APP_DEBUG')) 
            DB::listen(function($query) 
                File::append(
                    storage_path('/logs/query.log'),
                    $query->sql . ' [' . implode(', ', $query->bindings) . ']' . PHP_EOL
            );
            );
        
    

您将在将要创建的 /storage/logs/query.log 文件中找到每个查询的 sql 代码。 这有助于我理解和优化查询生成器语句,有时我也遇到过类似的问题。

【讨论】:

以上是关于REST API 的 Laravel DB 查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

在 laravel 中使用 REST API 的最佳身份验证方法是啥?

用于非授权连接的 Spring Security REST Api

如何使用 Laravel 的流畅查询构建器混合原始 SQL 和非原始 SQL

Laravel 5 REST API

php 使用Passport进行Laravel REST API身份验证:https://www.cloudways.com/blog/rest-api-laravel-passport-authen

简单的 Laravel 5 REST API