数据库:迁移
介绍
迁移就像是数据库的版本控制,允许你的团队修改和共享应用程序的数据库架构。迁移通常与 Laravel 的架构构建器配合使用,以构建应用程序的数据库架构。如果你曾经需要告诉队友手动向他们的本地数据库架构添加一列,那么你就遇到了数据库迁移所解决的问题。
Laravel 的 Schema
facade 提供了数据库无关的支持,用于在所有 Laravel 支持的数据库系统中创建和操作表。
生成迁移
要创建迁移,请使用 make:migration
Artisan 命令:
php artisan make:migration create_users_table
新的迁移将被放置在你的 database/migrations
目录中。每个迁移文件名都包含一个时间戳,这使得 Laravel 能够确定迁移的顺序。
可以使用 stub 发布 自定义迁移存根
--table
和 --create
选项也可以用来指示表的名称以及迁移是否会创建新表。这些选项会预填充生成的迁移存根文件中的指定表:
php artisan make:migration create_users_table --create=users
php artisan make:migration add_votes_to_users_table --table=users
如果你想为生成的迁移指定一个自定义输出路径,可以在执行 make:migration
命令时使用 --path
选项。给定的路径应该相对于应用程序的基路径。
迁移结构
迁移类包含两个方法:up
和 down
。up
方法用于向数据库添加新表、列或索引,而 down
方法应当反转 up
方法执行的操作。
在这两个方法中,你可以使用 Laravel 的架构构建器来表达性地创建和修改表。要了解架构构建器上所有可用的方法,请查看其文档。例如,以下迁移创建了一个 flights
表:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFlightsTable extends Migration
{
/**
* 运行迁移。
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* 反转迁移。
*
* @return void
*/
public function down()
{
Schema::drop('flights');
}
}
运行迁移
要运行所有未完成的迁移,请执行 migrate
Artisan 命令:
php artisan migrate
如果你正在使用 Homestead 虚拟机,你应该在虚拟机内运行此命令。
强制在生产环境中运行迁移
某些迁移操作是破坏性的,这意味着它们可能导致数据丢失。为了防止你在生产数据库上运行这些命令,执行命令前会提示你确认。要在没有提示的情况下强制运行命令,请使用 --force
标志:
php artisan migrate --force
回滚迁移
要回滚最新的迁移操作,可以使用 rollback
命令。此命令会回滚最后一个“批次”的迁移,其中可能包含多个迁移文件:
php artisan migrate:rollback
你可以通过为 rollback
命令提供 step
选项来回滚有限数量的迁移。例如,以下命令将回滚最近的五个迁移:
php artisan migrate:rollback --step=5
migrate:reset
命令将回滚应用程序的所有迁移:
php artisan migrate:reset
使用单个命令回滚并迁移
migrate:refresh
命令将回滚所有迁移,然后执行 migrate
命令。此命令有效地重新创建整个数据库:
php artisan migrate:refresh
// 刷新数据库并运行所有数据库种子...
php artisan migrate:refresh --seed
你可以通过为 refresh
命令提供 step
选项来回滚并重新迁移有限数量的迁移。例如,以下命令将回滚并重新迁移最近的五个迁移:
php artisan migrate:refresh --step=5
删除所有表并迁移
migrate:fresh
命令将从数据库中删除所有表,然后执行 migrate
命令:
php artisan migrate:fresh
php artisan migrate:fresh --seed
表
创建表
要创建新的数据库表,请在 Schema
facade 上使用 create
方法。create
方法接受两个参数:第一个是表的名称,第二个是一个 Closure
,它接收一个 Blueprint
对象,可以用来定义新表:
Schema::create('users', function (Blueprint $table) {
$table->id();
});
创建表时,你可以使用架构构建器的任何列方法来定义表的列。
检查表/列是否存在
你可以使用 hasTable
和 hasColumn
方法检查表或列是否存在:
if (Schema::hasTable('users')) {
//
}
if (Schema::hasColumn('users', 'email')) {
//
}
数据库连接和表选项
如果你想在非默认连接的数据库上执行架构操作,请使用 connection
方法:
Schema::connection('foo')->create('users', function (Blueprint $table) {
$table->id();
});
你可以在架构构建器上使用以下命令来定义表的选项:
命令 | 描述 |
---|---|
$table->engine = 'InnoDB'; | 指定表的存储引擎(MySQL)。 |
$table->charset = 'utf8mb4'; | 为表指定默认字符集(MySQL)。 |
$table->collation = 'utf8mb4_unicode_ci'; | 为表指定默认排序规则(MySQL)。 |
$table->temporary(); | 创建临时表(除 SQL Server 外)。 |
重命名/删除表
要重命名现有数据库表,请使用 rename
方法:
Schema::rename($from, $to);
要删除现有表,可以使用 drop
或 dropIfExists
方法:
Schema::drop('users');
Schema::dropIfExists('users');
重命名带有外键的表
在重命名表之前,你应该验证表上的任何外键约束在你的迁移文件中有一个显式名称,而不是让 Laravel 分配一个基于约定的名称。否则,外键约束名称将引用旧的表名。
列
创建列
可以在 Schema
facade 上使用 table
方法来更新现有表。与 create
方法类似,table
方法接受两个参数:表的名称和一个 Closure
,它接收一个 Blueprint
实例,你可以用来向表中添加列:
Schema::table('users', function (Blueprint $table) {
$table->string('email');
});
可用列类型
架构构建器包含多种列类型,你可以在构建表时指定这些类型:
命令 | 描述 |
---|---|
$table->id(); | $table->bigIncrements('id') 的别名。 |
$table->foreignId('user_id'); | $table->unsignedBigInteger('user_id') 的别名。 |
$table->bigIncrements('id'); | 自增的 UNSIGNED BIGINT(主键)等效列。 |
$table->bigInteger('votes'); | BIGINT 等效列。 |
$table->binary('data'); | BLOB 等效列。 |
$table->boolean('confirmed'); | BOOLEAN 等效列。 |
$table->char('name', 100); | 带长度的 CHAR 等效列。 |
$table->date('created_at'); | DATE 等效列。 |
$table->dateTime('created_at', 0); | 带精度(总位数)的 DATETIME 等效列。 |
$table->dateTimeTz('created_at', 0); | 带时区和精度(总位数)的 DATETIME 等效列。 |
$table->decimal('amount', 8, 2); | 带精度(总位数)和刻度(小数位数)的 DECIMAL 等效列。 |
$table->double('amount', 8, 2); | 带精度(总位数)和刻度(小数位数)的 DOUBLE 等效列。 |
$table->enum('level', ['easy', 'hard']); | ENUM 等效列。 |
$table->float('amount', 8, 2); | 带精度(总位数)和刻度(小数位数)的 FLOAT 等效列。 |
$table->geometry('positions'); | GEOMETRY 等效列。 |
$table->geometryCollection('positions'); | GEOMETRYCOLLECTION 等效列。 |
$table->increments('id'); | 自增的 UNSIGNED INTEGER(主键)等效列。 |
$table->integer('votes'); | INTEGER 等效列。 |
$table->ipAddress('visitor'); | IP 地址等效列。 |
$table->json('options'); | JSON 等效列。 |
$table->jsonb('options'); | JSONB 等效列。 |
$table->lineString('positions'); | LINESTRING 等效列。 |
$table->longText('description'); | LONGTEXT 等效列。 |
$table->macAddress('device'); | MAC 地址等效列。 |
$table->mediumIncrements('id'); | 自增的 UNSIGNED MEDIUMINT(主键)等效列。 |
$table->mediumInteger('votes'); | MEDIUMINT 等效列。 |
$table->mediumText('description'); | MEDIUMTEXT 等效列。 |
$table->morphs('taggable'); | 添加 taggable_id UNSIGNED BIGINT 和 taggable_type VARCHAR 等效列。 |
$table->uuidMorphs('taggable'); | 添加 taggable_id CHAR(36) 和 taggable_type VARCHAR(255) UUID 等效列。 |
$table->multiLineString('positions'); | MULTILINESTRING 等效列。 |
$table->multiPoint('positions'); | MULTIPOINT 等效列。 |
$table->multiPolygon('positions'); | MULTIPOLYGON 等效列。 |
$table->nullableMorphs('taggable'); | 添加 morphs() 列的可空版本。 |
$table->nullableUuidMorphs('taggable'); | 添加 uuidMorphs() 列的可空版本。 |
$table->nullableTimestamps(0); | timestamps() 方法的别名。 |
$table->point('position'); | POINT 等效列。 |
$table->polygon('positions'); | POLYGON 等效列。 |
$table->rememberToken(); | 添加可空的 remember_token VARCHAR(100) 等效列。 |
$table->set('flavors', ['strawberry', 'vanilla']); | SET 等效列。 |
$table->smallIncrements('id'); | 自增的 UNSIGNED SMALLINT(主键)等效列。 |
$table->smallInteger('votes'); | SMALLINT 等效列。 |
$table->softDeletes('deleted_at', 0); | 添加可空的 deleted_at TIMESTAMP 等效列,用于软删除,带精度(总位数)。 |
$table->softDeletesTz('deleted_at', 0); | 添加可空的 deleted_at TIMESTAMP(带时区)等效列,用于软删除,带精度(总位数)。 |
$table->string('name', 100); | 带长度的 VARCHAR 等效列。 |
$table->text('description'); | TEXT 等效列。 |
$table->time('sunrise', 0); | 带精度(总位数)的 TIME 等效列。 |
$table->timeTz('sunrise', 0); | 带时区和精度(总位数)的 TIME 等效列。 |
$table->timestamp('added_on', 0); | 带精度(总位数)的 TIMESTAMP 等效列。 |
$table->timestampTz('added_on', 0); | 带时区和精度(总位数)的 TIMESTAMP 等效列。 |
$table->timestamps(0); | 添加可空的 created_at 和 updated_at TIMESTAMP 等效列,带精度(总位数)。 |
$table->timestampsTz(0); | 添加可空的 created_at 和 updated_at TIMESTAMP(带时区)等效列,带精度(总位数)。 |
$table->tinyIncrements('id'); | 自增的 UNSIGNED TINYINT(主键)等效列。 |
$table->tinyInteger('votes'); | TINYINT 等效列。 |
$table->unsignedBigInteger('votes'); | UNSIGNED BIGINT 等效列。 |
$table->unsignedDecimal('amount', 8, 2); | 带精度(总位数)和刻度(小数位数)的 UNSIGNED DECIMAL 等效列。 |
$table->unsignedInteger('votes'); | UNSIGNED INTEGER 等效列。 |
$table->unsignedMediumInteger('votes'); | UNSIGNED MEDIUMINT 等效列。 |
$table->unsignedSmallInteger('votes'); | UNSIGNED SMALLINT 等效列。 |
$table->unsignedTinyInteger('votes'); | UNSIGNED TINYINT 等效列。 |
$table->uuid('id'); | UUID 等效列。 |
$table->year('birth_year'); | YEAR 等效列。 |
列修饰符
除了上面列出的列类型外,还有几个列“修饰符”可以在向数据库表添加列时使用。例如,要使列“可空”,可以使用 nullable
方法:
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable();
});
以下列表包含所有可用的列修饰符。此列表不包括索引修饰符:
修饰符 | 描述 |
---|---|
->after('column') | 将列放在另一个列“之后”(MySQL) |
->autoIncrement() | 将 INTEGER 列设置为自增(主键) |
->charset('utf8mb4') | 为列指定字符集(MySQL) |
->collation('utf8mb4_unicode_ci') | 为列指定排序规则(MySQL/PostgreSQL/SQL Server) |
->comment('my comment') | 为列添加注释(MySQL/PostgreSQL) |
->default($value) | 为列指定“默认”值 |
->first() | 将列放在表的“首位”(MySQL) |
->nullable($value = true) | 允许(默认)向列插入 NULL 值 |
->storedAs($expression) | 创建存储生成的列(MySQL) |
->unsigned() | 将 INTEGER 列设置为 UNSIGNED(MySQL) |
->useCurrent() | 将 TIMESTAMP 列设置为使用 CURRENT_TIMESTAMP 作为默认值 |
->virtualAs($expression) | 创建虚拟生成的列(MySQL) |
->generatedAs($expression) | 使用指定的序列选项创建标识列(PostgreSQL) |
->always() | 定义序列值相对于输入的优先级(PostgreSQL) |
默认表达式
default
修饰符接受一个值或一个 \Illuminate\Database\Query\Expression
实例。使用 Expression
实例将防止将值用引号括起来,并允许你使用数据库特定的函数。在需要为 JSON 列分配默认值时,这特别有用:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Migrations\Migration;
class CreateFlightsTable extends Migration
{
/**
* 运行迁移。
*
* @return void
*/
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->json('movies')->default(new Expression('(JSON_ARRAY())'));
$table->timestamps();
});
}
}
默认表达式的支持取决于你的数据库驱动程序、数据库版本和字段类型。请参考相应的文档以了解兼容性。此外,使用数据库特定的函数可能会将你紧密耦合到特定的驱动程序。
修改列
前提条件
在修改列之前,请确保在你的 composer.json
文件中添加 doctrine/dbal
依赖项。Doctrine DBAL 库用于确定列的当前状态并创建所需的 SQL 查询以进行必要的调整:
composer require doctrine/dbal
更新列属性
change
方法允许你修改现有列的类型和属性。例如,你可能希望增加 string
列的大小。要查看 change
方法的实际应用,让我们将 name
列的大小从 25 增加到 50:
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->change();
});
我们还可以将列修改为可空:
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->nullable()->change();
});
只有以下列类型可以“更改”:bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger, unsignedSmallInteger 和 uuid。
重命名列
要重命名列,可以在架构构建器上使用 renameColumn
方法。在重命名列之前,请确保在你的 composer.json
文件中添加 doctrine/dbal
依赖项:
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});
当前不支持在同时具有 enum
类型列的表中重命名任何列。
删除列
要删除列,请在架构构建器上使用 dropColumn
方法。在从 SQLite 数据库中删除列之前,你需要在你的 composer.json
文件中添加 doctrine/dbal
依赖项,并在终端中运行 composer update
命令以安装库:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
});
你可以通过将列名数组传递给 dropColumn
方法来从表中删除多个列:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['votes', 'avatar', 'location']);
});
在使用 SQLite 数据库时,在单个迁移中删除或修改多个列是不支持的。
可用命令别名
命令 | 描述 |
---|---|
$table->dropMorphs('morphable'); | 删除 morphable_id 和 morphable_type 列。 |
$table->dropRememberToken(); | 删除 remember_token 列。 |
$table->dropSoftDeletes(); | 删除 deleted_at 列。 |
$table->dropSoftDeletesTz(); | dropSoftDeletes() 方法的别名。 |
$table->dropTimestamps(); | 删除 created_at 和 updated_at 列。 |
$table->dropTimestampsTz(); | dropTimestamps() 方法的别名。 |
索引
创建索引
Laravel 架构构建器支持多种类型的索引。以下示例创建了一个新的 email
列,并指定其值应唯一。要创建索引,我们可以将 unique
方法链接到列定义上:
$table->string('email')->unique();
或者,你可以在定义列后创建索引。例如:
$table->unique('email');
你甚至可以将列数组传递给索引方法以创建复合(或组合)索引:
$table->index(['account_id', 'created_at']);
Laravel 将根据表、列名和索引类型自动生成索引名称,但你可以将第二个参数传递给方法以指定索引名称:
$table->unique('email', 'unique_email');
可用索引类型
每个索引方法接受一个可选的第二个参数来指定索引的名称。如果省略,将根据用于索引的表和列名以及索引类型派生名称。
命令 | 描述 |
---|---|
$table->primary('id'); | 添加主键。 |
$table->primary(['id', 'parent_id']); | 添加复合键。 |
$table->unique('email'); | 添加唯一索引。 |
$table->index('state'); | 添加普通索引。 |
$table->spatialIndex('location'); | 添加空间索引。(除 SQLite 外) |
索引长度与 MySQL / MariaDB
Laravel 默认使用 utf8mb4
字符集,其中包括支持在数据库中存储“表情符号”。如果你运行的 MySQL 版本低于 5.7.7 或 MariaDB 版本低于 10.2.2,你可能需要手动配置迁移生成的默认字符串长度,以便 MySQL 为它们创建索引。你可以通过在 AppServiceProvider
中调用 Schema::defaultStringLength
方法来配置此项:
use Illuminate\Support\Facades\Schema;
/**
* 启动任何应用程序服务。
*
* @return void
*/
public function boot()
{
Schema::defaultStringLength(191);
}
或者,你可以为数据库启用 innodb_large_prefix
选项。请参考数据库的文档以了解如何正确启用此选项。
重命名索引
要重命名索引,可以使用 renameIndex
方法。此方法接受当前索引名称作为第一个参数,期望的新名称作为第二个参数:
$table->renameIndex('from', 'to')
删除索引
要删除索引,必须指定索引的名称。默认情况下,Laravel 会根据表名、索引列名和索引类型自动分配索引名称。以下是一些示例:
命令 | 描述 |
---|---|
$table->dropPrimary('users_id_primary'); | 从“users”表中删除主键。 |
$table->dropUnique('users_email_unique'); | 从“users”表中删除唯一索引。 |
$table->dropIndex('geo_state_index'); | 从“geo”表中删除基本索引。 |
$table->dropSpatialIndex('geo_location_spatialindex'); | 从“geo”表中删除空间索引(除 SQLite 外)。 |
如果你将列数组传递给删除索引的方法,常规索引名称将根据表名、列和键类型生成:
Schema::table('geo', function (Blueprint $table) {
$table->dropIndex(['state']); // 删除索引 'geo_state_index'
});
外键约束
Laravel 还提供了创建外键约束的支持,这些约束用于在数据库级别强制参照完整性。例如,让我们在 posts
表上定义一个 user_id
列,该列引用 users
表上的 id
列:
Schema::table('posts', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
由于这种语法相当冗长,Laravel 提供了额外的、更简洁的方法,使用约定来提供更好的开发者体验。上面的示例可以这样写:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained();
});
foreignId
方法是 unsignedBigInteger
的别名,而 constrained
方法将使用约定来确定被引用的表和列名。如果你的表名不符合约定,可以通过将表名作为参数传递给 constrained
方法来指定表名:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained('users');
});
你还可以为约束的“on delete”和“on update”属性指定所需的操作:
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
任何额外的列修饰符必须在 constrained
之前调用:
$table->foreignId('user_id')
->nullable()
->constrained();
要删除外键,可以使用 dropForeign
方法,将要删除的外键约束作为参数传递。外键约束使用与索引相同的命名约定,基于表名和约束中的列,后缀为“_foreign”:
$table->dropForeign('posts_user_id_foreign');
或者,你可以将包含外键列名的数组传递给 dropForeign
方法。数组将根据 Laravel 架构构建器使用的约束名称约定自动转换:
$table->dropForeign(['user_id']);
你可以通过使用以下方法在迁移中启用或禁用外键约束:
Schema::enableForeignKeyConstraints();
Schema::disableForeignKeyConstraints();