【发布时间】: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。不是来自生成的查询。
标签: php mysql laravel eloquent mariadb