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`)
或 ->whereDate('date', ...)
)的情况下进行查询,而是使用 ->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)->with(['artist', 'song'])->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
php 使用Passport进行Laravel REST API身份验证:https://www.cloudways.com/blog/rest-api-laravel-passport-authen