mysql - 更快的 Laravel 迁移,用于测试具有大量表的项目
问题描述
我刚刚开始从事这个零测试的大项目。我们的想法是对每个新功能和/或错误进行 TDD,并且随着时间的推移,我们将增加测试覆盖率。
我不使用 SQLite 内存数据库进行测试。我更喜欢使用 MySql,因为它与我在生产中使用的数据库相同。通常,在小项目中,这没有问题,但对于大项目,它是!
我面临的问题与性能有关,一个在磁盘中运行的普通 MySql 实例(M.2 SSD)大约需要90 秒来运行这个大项目的所有迁移。有超过 200 个表要迁移,有很多关系。
这个问题的解决方案是在内存中设置 MySql,使用tmpfs
docker。这个技巧让我将迁移时间减少到只有10 秒,还不错,但如果你只想运行 1 次测试真的很烦人!10 秒迁移,几毫秒测试。
Laravel 8 刚刚带来了一个新功能,叫做Schema Dump
:https ://github.com/laravel/framework/pull/32275
我刚刚看到这个新功能,它真的让我很开心,非常好!它将帮助很多人并节省大量时间。如果您有很多迁移,您可以显着减少迁移它们的时间。否则,这并不能解决我的问题。该项目的迁移次数非常接近每个表 1 次迁移。这里不需要优化任何东西。
出于好奇,我拍摄了数据库的 Schema 快照并尝试使用 MySql 命令行恢复它。运行架构还原并设置所有内容大约需要3 秒:
mysql -h 127.0.0.1 -u root -P 3331 -p default < database/migrations.sql
目前,测试数据库一直保持迁移状态,这样我的测试流程(一次测试一次运行)保持超快!
我喜欢认为单个测试应该像一个按钮,你按下它,它会立即亮起绿色或红色。
我的问题: - 是否可以减少具有大量表的项目的迁移时间?(仅用于测试)
我没有关于 MySql 的内幕知识,也许我遗漏了一些东西......
解决方案
只是一个提示
当我们有不同的功能要测试时,我也遇到过这种情况,并且在每次测试后重置数据库通常很有用,这样之前测试的数据就不会干扰后续测试。
我采用了以下方法,希望您能从中得到一些帮助,只是分享提示。
如果您的大型项目有 40 个表或更多表,那么从头开始重写和部署所有迁移可能并不理想。在这种情况下,解决方案是将新迁移的数据库导出为 SQL“快照”。与原始迁移相比,这个包含所有迁移作为原始 SQL 查询的文件将显着加快解析和执行速度。如果你想测试你网站的每一个特性,那么Laravel's RefreshDatabase trait
它是有意义的,实际上它是用来用一个新迁移的数据库开始每一个测试的。在后台,Laravel 在每次测试之前运行以下代码:
protected function refreshTestDatabase()
{
if (! RefreshDatabaseState::$migrated) {
$this->artisan('migrate:fresh');
RefreshDatabaseState::$migrated = true;
}
$this->beginDatabaseTransaction();
}
如您所见,迁移只会在第一次测试之前运行一次。第一次测试后,使用数据库事务快速恢复到初始迁移的数据库状态。这在运行多个测试时节省了大量时间。但是,运行这些初始迁移的时间可能会显着延迟您的测试结果。如果您只想运行一个“快速”测试,这尤其令人讨厌
执行
首先清除数据库并使用php artisan migrate:fresh
. 然后打开您首选的数据库客户端并导出(或备份)空数据库。您应该只剩下一个 SQL 文件。让我们将该文件重命名为migrations_2019_01_10.sql
并将其放在我们应用程序的数据库目录中。接下来,我们必须在RefreshDatabase trait
. 您可以将整个特征复制到您自己的代码库中,也可以简单地覆盖您自己的TestCase.php
. 最终看起来像这样:
abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase
{
use CreatesApplication;
use RefreshDatabase;
protected function refreshTestDatabase()
{
if (! RefreshDatabaseState::$migrated) {
DB::unprepared(file_get_contents(database_path('migrations_2019_01_10.sql')));
$this->artisan('migrate');
$this->app[Kernel::class]->setArtisan(null);
RefreshDatabaseState::$migrated = true;
}
$this->beginDatabaseTransaction();
}
}
如您所见,我将命令保留在那里以运行在我们的快照migrate
之后可能添加的任何新迁移。migrations.sql
这样您就不必在每次添加迁移时都导出迁移的数据库。只要记住每隔一段时间准备一个新的快照。
推荐阅读
- kotlin - 构建数据库表和@Relation 以从数据库实体创建问卷?(科特林,房间)
- php - 为什么 'ini_get_all()' 与 'php -i' 和 'php.ini' 有区别
- c# - 在 c# 中从 Cypher Result 反序列化为嵌套对象
- python - 如果没有找到 html 元素,让 Python 继续
- c++ - 用 lambda 和 __attribute__ 理解宏代码语句
- filtering - 最新日期的 Talend 过滤器行
- grafana - 如何在 Fluentd 匹配标签 loki 中评估表达式
- next.js - 带有 nextjs 的 SSG 会导致大量获取 404 的 json 文件
- reference - 标题引用静态而不是在更改顺序时更改
- python - python中未腌制集中的意外性能下降