数据库迁移 本文未发布 发布文章
数据库:迁移
简介
迁移就像数据库的版本控制,让团队能够定义和共享应用的数据库结构。如果你曾经需要告诉队友,在拉取代码更改后手动向本地数据库添加一列,那么你就遇到了数据库迁移要解决的问题。
Laravel 的 Schema 门面提供了不依赖具体数据库的接口,可以在 Laravel 支持的所有数据库系统中创建和操作表。迁移通常使用此门面创建和修改数据库表及列。
生成迁移
你可以使用 make:migration Artisan 命令生成数据库迁移。新迁移文件会放在 database/migrations 目录中。每个文件名都包含时间戳,Laravel 据此确定迁移的执行顺序:
php artisan make:migration create_flights_table
Laravel 会根据迁移名称推断表名,以及该迁移是否用于创建新表。如果能够确定表名,就会将其预填到生成的迁移文件中。否则,你可以在迁移文件中手动指定表名。
如果希望自定义生成迁移的路径,可以在执行 make:migration 时使用 --path 选项。该路径应相对于应用的根目录。
[!NOTE]
你可以通过发布 stub 模板来自定义迁移模板。
合并迁移
随着应用不断开发,迁移文件会越来越多,database/migrations 目录中可能积累数百个文件。你可以将这些迁移「合并」为一个 SQL 文件。首先执行 schema:dump 命令:
php artisan schema:dump
# 导出当前数据库结构,并清理所有现有迁移……
php artisan schema:dump --prune
执行该命令后,Laravel 会将数据库结构文件写入应用的 database/schema 目录,文件名与数据库连接对应。此后,如果数据库尚未执行过任何迁移,Laravel 会先执行当前连接对应的结构文件中的 SQL 语句,再执行未包含在结构导出文件中的剩余迁移。
如果应用测试使用的数据库连接与本地开发时不同,应确保也为测试连接导出数据库结构文件,以便测试能够构建数据库。你可以在导出本地开发连接的结构后再执行此操作:
php artisan schema:dump
php artisan schema:dump --database=testing --prune
应将数据库结构文件提交到版本控制,让新加入团队的开发者能够快速创建应用的初始数据库结构。
[!WARNING]
合并迁移仅适用于 MariaDB、MySQL、PostgreSQL 和 SQLite,并且需要使用数据库的命令行客户端。
迁移结构
迁移类包含 up 和 down 两个方法。up 用于向数据库添加表、列或索引,down 则应撤销 up 执行的操作。
在这两个方法中,你可以使用 Laravel 数据库结构构建器,以清晰的语法创建和修改表。关于 Schema 构建器的所有可用方法,请参阅其文档。例如,以下迁移会创建一个 flights 表:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 运行迁移。
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* 回滚迁移。
*/
public function down(): void
{
Schema::drop('flights');
}
};
设置迁移使用的连接
如果迁移需要使用应用默认连接以外的数据库连接,应设置迁移的 $connection 属性:
/**
* 迁移应使用的数据库连接。
*
* @var string
*/
protected $connection = 'pgsql';
/**
* 运行迁移。
*/
public function up(): void
{
// ...
}
跳过迁移
有时,迁移用于支持尚未启用的功能,你可能不希望立即运行它。这时可以在迁移中定义 shouldRun 方法。如果该方法返回 false,就会跳过此迁移:
use App\Models\Flight;
use Laravel\Pennant\Feature;
/**
* 判断是否应运行此迁移。
*/
public function shouldRun(): bool
{
return Feature::active(Flight::class);
}
运行迁移
要运行所有待执行的迁移,请执行 migrate Artisan 命令:
php artisan migrate
要查看哪些迁移已执行、哪些仍待执行,可以使用 migrate:status Artisan 命令:
php artisan migrate:status
向 migrate 命令传入 --step 选项后,每个迁移都会作为独立批次执行,方便之后通过 migrate:rollback 命令逐个回滚:
php artisan migrate --step
如果希望查看迁移将执行的 SQL 语句,而不实际运行迁移,可以为 migrate 命令添加 --pretend 标志:
php artisan migrate --pretend
隔离迁移执行
如果将应用部署到多台服务器,并在部署过程中运行迁移,通常应避免两台服务器同时迁移数据库。调用 migrate 命令时,可以使用 isolated 选项来避免这种情况。
使用 isolated 选项后,Laravel 会先通过应用的缓存驱动获取原子锁,再运行迁移。持锁期间,其他 migrate 命令不会执行迁移,但仍会以成功状态码退出:
php artisan migrate --isolated
[!WARNING]
使用此功能时,应用的默认缓存驱动必须为memcached、redis、dynamodb、database、file或array,并且所有服务器都必须连接到同一个中央缓存服务器。
在生产环境强制运行迁移
某些迁移操作可能导致数据丢失。为防止误操作生产数据库,执行这些命令前会要求确认。如果要跳过提示并强制执行,可以使用 --force 标志:
php artisan migrate --force
回滚迁移
要回滚最近一次迁移操作,可以使用 rollback Artisan 命令。该命令会回滚最后一个「批次」的迁移,其中可能包含多个迁移文件:
php artisan migrate:rollback
你可以通过 rollback 命令的 step 选项,只回滚指定数量的迁移。例如,以下命令会回滚最近五个迁移:
php artisan migrate:rollback --step=5
你也可以使用 rollback 命令的 batch 选项回滚指定批次。该选项对应应用中 migrations 数据表的批次值。例如,以下命令会回滚第 3 批次的所有迁移:
php artisan migrate:rollback --batch=3
如果希望查看回滚将执行的 SQL 语句,而不实际运行,可以为 migrate:rollback 命令添加 --pretend 标志:
php artisan migrate:rollback --pretend
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
默认情况下,migrate:fresh 只删除默认数据库连接中的表。你可以通过 --database 选项指定要迁移的连接。连接名称应对应应用的 database 配置文件中定义的连接:
php artisan migrate:fresh --database=admin
[!WARNING]
migrate:fresh会删除数据库中的所有表,不区分表前缀。如果数据库与其他应用共用,应谨慎使用此命令。
表
创建表
要创建数据库表,请使用 Schema 门面的 create 方法。该方法接受两个参数:第一个是表名,第二个是接收 Blueprint 对象的闭包,你可以在闭包中定义新表:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->timestamps();
});
创建表时,可以使用数据库结构构建器提供的各种列方法定义表中的列。
判断表或列是否存在
你可以使用 hasTable、hasColumn 和 hasIndex 方法,判断表、列或索引是否存在:
if (Schema::hasTable('users')) {
// 「users」表存在……
}
if (Schema::hasColumn('users', 'email')) {
// 「users」表存在,并且包含「email」列……
}
if (Schema::hasIndex('users', ['email'], 'unique')) {
// 「users」表存在,并且「email」列有唯一索引……
}
数据库连接和表选项
要在应用默认连接以外的数据库连接上操作数据库结构,请使用 connection 方法:
Schema::connection('sqlite')->create('users', function (Blueprint $table) {
$table->id();
});
你还可以通过其他属性和方法设置建表选项。使用 MariaDB 或 MySQL 时,可以通过 engine 属性指定表的存储引擎:
Schema::create('users', function (Blueprint $table) {
$table->engine('InnoDB');
// ...
});
使用 MariaDB 或 MySQL 时,可以通过 charset 和 collation 属性指定新表的字符集和排序规则:
Schema::create('users', function (Blueprint $table) {
$table->charset('utf8mb4');
$table->collation('utf8mb4_unicode_ci');
// ...
});
temporary 方法可以将表指定为「临时表」。临时表仅对当前连接的数据库会话可见,并在连接关闭时自动删除:
Schema::create('calculations', function (Blueprint $table) {
$table->temporary();
// ...
});
要为数据库表添加「注释」,可以调用表实例的 comment 方法。目前仅 MariaDB、MySQL 和 PostgreSQL 支持表注释:
Schema::create('calculations', function (Blueprint $table) {
$table->comment('Business calculations');
// ...
});
更新表
Schema 门面的 table 方法可以更新现有表。与 create 一样,该方法接受表名和闭包两个参数。闭包会接收一个 Blueprint 实例,你可以用它向表中添加列或索引:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->integer('votes');
});
重命名与删除表
要重命名现有数据库表,请使用 rename 方法:
use Illuminate\Support\Facades\Schema;
Schema::rename($from, $to);
要删除现有表,可以使用 drop 或 dropIfExists 方法:
Schema::drop('users');
Schema::dropIfExists('users');
重命名带有外键的表
重命名表之前,应确认迁移文件中已为表上的所有外键约束明确指定名称,而不是让 Laravel 按约定生成名称。否则,外键约束名称仍会引用旧表名。
列
创建列
Schema 门面的 table 方法可以更新现有表。与 create 一样,该方法接受表名和闭包两个参数。闭包会接收一个 Illuminate\Database\Schema\Blueprint 实例,你可以用它向表中添加列:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->integer('votes');
});
可用列类型
数据库结构构建器的蓝图对象提供了多种方法,对应可添加到数据库表中的不同列类型。下方列出了所有可用方法:
布尔类型
字符串与文本类型
数值类型
bigIncrements
bigInteger
decimal
double
float
id
increments
integer
mediumIncrements
mediumInteger
smallIncrements
smallInteger
tinyIncrements
tinyInteger
unsignedBigInteger
unsignedInteger
unsignedMediumInteger
unsignedSmallInteger
unsignedTinyInteger
日期与时间类型
二进制类型
对象与 JSON 类型
UUID 与 ULID 类型
空间类型
关系类型
特殊类型
bigIncrements()
bigIncrements 方法创建一个等效于 UNSIGNED BIGINT 的自增主键列:
$table->bigIncrements('id');
bigInteger()
bigInteger 方法创建一个等效于 BIGINT 的列:
$table->bigInteger('votes');
binary()
binary 方法创建一个等效于 BLOB 的列:
$table->binary('photo');
使用 MySQL、MariaDB 或 SQL Server 时,可以传入 length 和 fixed 参数,创建等效于 VARBINARY 或 BINARY 的列:
$table->binary('data', length: 16); // VARBINARY(16)
$table->binary('data', length: 16, fixed: true); // BINARY(16)
boolean()
boolean 方法创建一个等效于 BOOLEAN 的列:
$table->boolean('confirmed');
char()
char 方法创建一个等效于 CHAR、长度为指定值的列:
$table->char('name', length: 100);
dateTimeTz()
dateTimeTz 方法创建一个等效于 DATETIME(带时区)的列,可以指定小数秒精度:
$table->dateTimeTz('created_at', precision: 0);
dateTime()
dateTime 方法创建一个等效于 DATETIME 的列,可以指定小数秒精度:
$table->dateTime('created_at', precision: 0);
date()
date 方法创建一个等效于 DATE 的列:
$table->date('created_at');
decimal()
decimal 方法创建一个等效于 DECIMAL 的列,并指定精度(总位数)和标度(小数位数):
$table->decimal('amount', total: 8, places: 2);
double()
double 方法创建一个等效于 DOUBLE 的列:
$table->double('amount');
enum()
enum 方法创建一个等效于 ENUM 的列,并指定允许的值:
$table->enum('difficulty', ['easy', 'hard']);
你也可以使用 Enum::cases() 方法,无需手动定义允许值的数组:
use App\Enums\Difficulty;
$table->enum('difficulty', Difficulty::cases());
float()
float 方法创建一个等效于 FLOAT、精度为指定值的列:
$table->float('amount', precision: 53);
foreignId()
foreignId 方法创建一个等效于 UNSIGNED BIGINT 的列:
$table->foreignId('user_id');
foreignIdFor()
foreignIdFor 方法为指定模型类添加一个 {column}_id 列。根据模型主键的类型,列类型会是 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26):
$table->foreignIdFor(User::class);
foreignUlid()
foreignUlid 方法创建一个等效于 ULID 的列:
$table->foreignUlid('user_id');
foreignUuid()
foreignUuid 方法创建一个等效于 UUID 的列:
$table->foreignUuid('user_id');
foreignUuidFor()
foreignUuidFor 方法为指定模型类添加一个等效于 UUID 的 {column}_id 列:
$table->foreignUuidFor(User::class);
geography()
geography 方法创建一个等效于 GEOGRAPHY 的列,并指定空间类型和 SRID(空间参考系统标识符):
$table->geography('coordinates', subtype: 'point', srid: 4326);
[!NOTE]
空间类型的支持取决于数据库驱动,请参阅相应数据库的文档。如果应用使用 PostgreSQL,必须先安装 PostGIS 扩展,才能使用geography方法。
geometry()
geometry 方法创建一个等效于 GEOMETRY 的列,并指定空间类型和 SRID(空间参考系统标识符):
$table->geometry('positions', subtype: 'point', srid: 0);
[!NOTE]
空间类型的支持取决于数据库驱动,请参阅相应数据库的文档。如果应用使用 PostgreSQL,必须先安装 PostGIS 扩展,才能使用geometry方法。
id()
id 方法是 bigIncrements 的别名,默认创建名为 id 的列。你也可以传入列名,为其指定其他名称:
$table->id();
increments()
increments 方法创建一个等效于 UNSIGNED INTEGER 的自增主键列:
$table->increments('id');
integer()
integer 方法创建一个等效于 INTEGER 的列:
$table->integer('votes');
ipAddress()
ipAddress 方法创建一个等效于 VARCHAR 的列:
$table->ipAddress('visitor');
使用 PostgreSQL 时,会创建 INET 列。
json()
json 方法创建一个等效于 JSON 的列:
$table->json('options');
使用 SQLite 时,会创建 TEXT 列。
jsonb()
jsonb 方法创建一个等效于 JSONB 的列:
$table->jsonb('options');
使用 SQLite 时,会创建 TEXT 列。
longText()
longText 方法创建一个等效于 LONGTEXT 的列:
$table->longText('description');
使用 MySQL 或 MariaDB 时,可以将列的字符集设为 binary,创建等效于 LONGBLOB 的列:
$table->longText('data')->charset('binary'); // LONGBLOB
macAddress()
macAddress 方法创建一个用于存储 MAC 地址的列。PostgreSQL 等数据库系统具有专门存储此类数据的列类型,其他数据库系统则使用等效的字符串列:
$table->macAddress('device');
mediumIncrements()
mediumIncrements 方法创建一个等效于 UNSIGNED MEDIUMINT 的自增主键列:
$table->mediumIncrements('id');
mediumInteger()
mediumInteger 方法创建一个等效于 MEDIUMINT 的列:
$table->mediumInteger('votes');
mediumText()
mediumText 方法创建一个等效于 MEDIUMTEXT 的列:
$table->mediumText('description');
使用 MySQL 或 MariaDB 时,可以将列的字符集设为 binary,创建等效于 MEDIUMBLOB 的列:
$table->mediumText('data')->charset('binary'); // MEDIUMBLOB
morphs()
morphs 是一个便捷方法,会添加一个等效于 VARCHAR 的 {column}_type 列和一个 {column}_id 列。根据模型主键的类型,{column}_id 的列类型会是 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26)。
该方法用于定义多态 Eloquent 关系所需的列。以下示例会创建 taggable_type 和 taggable_id 列:
$table->morphs('taggable');
nullableMorphs()
该方法与 morphs 类似,但创建的列允许为 null:
$table->nullableMorphs('taggable');
nullableUlidMorphs()
该方法与 ulidMorphs 类似,但创建的列允许为 null:
$table->nullableUlidMorphs('taggable');
nullableUuidMorphs()
该方法与 uuidMorphs 类似,但创建的列允许为 null:
$table->nullableUuidMorphs('taggable');
rememberToken()
rememberToken 方法创建一个允许为 null、等效于 VARCHAR(100) 的列,用于存储「记住我」功能的认证令牌:
$table->rememberToken();
set()
set 方法创建一个等效于 SET 的列,并指定允许的值列表:
$table->set('flavors', ['strawberry', 'vanilla']);
smallIncrements()
smallIncrements 方法创建一个等效于 UNSIGNED SMALLINT 的自增主键列:
$table->smallIncrements('id');
smallInteger()
smallInteger 方法创建一个等效于 SMALLINT 的列:
$table->smallInteger('votes');
softDeletesTz()
softDeletesTz 方法添加一个允许为 null、等效于 TIMESTAMP(带时区)的 deleted_at 列,可以指定小数秒精度。该列用于存储 Eloquent「软删除」功能所需的 deleted_at 时间戳:
$table->softDeletesTz('deleted_at', precision: 0);
softDeletes()
softDeletes 方法添加一个允许为 null、等效于 TIMESTAMP 的 deleted_at 列,可以指定小数秒精度。该列用于存储 Eloquent「软删除」功能所需的 deleted_at 时间戳:
$table->softDeletes('deleted_at', precision: 0);
string()
string 方法创建一个等效于 VARCHAR、长度为指定值的列:
$table->string('name', length: 100);
text()
text 方法创建一个等效于 TEXT 的列:
$table->text('description');
使用 MySQL 或 MariaDB 时,可以将列的字符集设为 binary,创建等效于 BLOB 的列:
$table->text('data')->charset('binary'); // BLOB
timeTz()
timeTz 方法创建一个等效于 TIME(带时区)的列,可以指定小数秒精度:
$table->timeTz('sunrise', precision: 0);
time()
time 方法创建一个等效于 TIME 的列,可以指定小数秒精度:
$table->time('sunrise', precision: 0);
timestampTz()
timestampTz 方法创建一个等效于 TIMESTAMP(带时区)的列,可以指定小数秒精度:
$table->timestampTz('added_at', precision: 0);
timestamp()
timestamp 方法创建一个等效于 TIMESTAMP 的列,可以指定小数秒精度:
$table->timestamp('added_at', precision: 0);
timestampsTz()
timestampsTz 方法创建 created_at 和 updated_at 两个等效于 TIMESTAMP(带时区)的列,可以指定小数秒精度:
$table->timestampsTz(precision: 0);
timestamps()
timestamps 方法创建 created_at 和 updated_at 两个等效于 TIMESTAMP 的列,可以指定小数秒精度:
$table->timestamps(precision: 0);
tinyIncrements()
tinyIncrements 方法创建一个等效于 UNSIGNED TINYINT 的自增主键列:
$table->tinyIncrements('id');
tinyInteger()
tinyInteger 方法创建一个等效于 TINYINT 的列:
$table->tinyInteger('votes');
tinyText()
tinyText 方法创建一个等效于 TINYTEXT 的列:
$table->tinyText('notes');
使用 MySQL 或 MariaDB 时,可以将列的字符集设为 binary,创建等效于 TINYBLOB 的列:
$table->tinyText('data')->charset('binary'); // TINYBLOB
unsignedBigInteger()
unsignedBigInteger 方法创建一个等效于 UNSIGNED BIGINT 的列:
$table->unsignedBigInteger('votes');
unsignedInteger()
unsignedInteger 方法创建一个等效于 UNSIGNED INTEGER 的列:
$table->unsignedInteger('votes');
unsignedMediumInteger()
unsignedMediumInteger 方法创建一个等效于 UNSIGNED MEDIUMINT 的列:
$table->unsignedMediumInteger('votes');
unsignedSmallInteger()
unsignedSmallInteger 方法创建一个等效于 UNSIGNED SMALLINT 的列:
$table->unsignedSmallInteger('votes');
unsignedTinyInteger()
unsignedTinyInteger 方法创建一个等效于 UNSIGNED TINYINT 的列:
$table->unsignedTinyInteger('votes');
ulidMorphs()
ulidMorphs 是一个便捷方法,会添加一个等效于 VARCHAR 的 {column}_type 列,以及一个等效于 CHAR(26) 的 {column}_id 列。
该方法用于定义使用 ULID 标识符的多态 Eloquent 关系所需的列。以下示例会创建 taggable_type 和 taggable_id 列:
$table->ulidMorphs('taggable');
uuidMorphs()
uuidMorphs 是一个便捷方法,会添加一个等效于 VARCHAR 的 {column}_type 列,以及一个等效于 CHAR(36) 的 {column}_id 列。
该方法用于定义使用 UUID 标识符的多态 Eloquent 关系所需的列。以下示例会创建 taggable_type 和 taggable_id 列:
$table->uuidMorphs('taggable');
ulid()
ulid 方法创建一个等效于 ULID 的列:
$table->ulid('id');
uuid()
uuid 方法创建一个等效于 UUID 的列:
$table->uuid('id');
vector()
vector 方法创建一个等效于 vector 的列:
$table->vector('embedding', dimensions: 1536);
使用 pgvector 扩展的 PostgreSQL 连接以及 MariaDB 11.7 及更高版本支持向量列。使用 PostgreSQL 时,必须先加载 pgvector,才能创建 vector 列:
Schema::ensureVectorExtensionExists();
要加快向量相似度查询,可以为列添加向量索引。在 vector 列上调用 index 方法,会创建使用余弦距离的向量索引:
$table->vector('embedding', dimensions: 1536)->index();
year()
year 方法创建一个等效于 YEAR 的列:
$table->year('birth_year');
列修饰符
除了上述列类型,向数据库表添加列时,还可以使用各种列「修饰符」。例如,要允许列的值为 null,可以使用 nullable 方法:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable();
});
下表列出了所有可用的列修饰符,不包含索引修饰符:
| 修饰符 | 说明 |
|---|---|
->after('column') |
将列放在另一列之后(MariaDB / MySQL)。 |
->autoIncrement() |
将 INTEGER 列设为自增主键。 |
->charset('utf8mb4') |
指定列的字符集(MariaDB / MySQL)。 |
->collation('utf8mb4_unicode_ci') |
指定列的排序规则。 |
->comment('my comment') |
为列添加注释(MariaDB / MySQL / PostgreSQL)。 |
->default($value) |
指定列的默认值。 |
->first() |
将列放在表的首位(MariaDB / MySQL)。 |
->from($integer) |
设置自增字段的起始值(MariaDB / MySQL / PostgreSQL)。 |
->instant() |
使用即时操作添加或修改列(MySQL)。 |
->invisible() |
使列对 SELECT * 查询不可见(MariaDB / MySQL)。 |
->lock($mode) |
指定列操作的锁模式(MySQL)。 |
->nullable($value = true) |
允许向列中插入 NULL 值。 |
->storedAs($expression) |
创建存储生成列(MariaDB / MySQL / PostgreSQL / SQLite)。 |
->unsigned() |
将 INTEGER 列设为 UNSIGNED(MariaDB / MySQL)。 |
->using($expression) |
更改列类型时指定类型转换表达式(PostgreSQL)。 |
->useCurrent() |
将 TIMESTAMP 列的默认值设为 CURRENT_TIMESTAMP。 |
->useCurrentOnUpdate() |
更新记录时将 TIMESTAMP 列设为 CURRENT_TIMESTAMP(MariaDB / MySQL)。 |
->virtualAs($expression) |
创建虚拟生成列(MariaDB / MySQL / SQLite)。 |
->generatedAs($expression) |
使用指定的序列选项创建标识列(PostgreSQL)。 |
->always() |
为标识列设置序列值优先于输入值(PostgreSQL)。 |
默认值表达式
default 修饰符接受一个值或 Illuminate\Database\Query\Expression 实例。使用 Expression 可以避免 Laravel 给值加上引号,从而使用数据库专用函数。这在为 JSON 列设置默认值时尤其有用:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* 运行迁移。
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->json('movies')->default(new Expression('(JSON_ARRAY())'));
$table->timestamps();
});
}
};
[!WARNING]
默认值表达式的支持取决于数据库驱动、数据库版本和字段类型,请参阅相应数据库的文档。
列顺序
使用 MariaDB 或 MySQL 时,可以通过 after 方法在现有列之后添加列:
$table->after('password', function (Blueprint $table) {
$table->string('address_line1');
$table->string('address_line2');
$table->string('city');
});
即时列操作
使用 MySQL 时,可以在列定义后链式调用 instant 修饰符,指定使用 MySQL 的「instant」算法添加或修改列。该算法允许在不重建整张表的情况下执行某些结构变更,因此无论表多大,都能近乎即时完成:
$table->string('name')->nullable()->instant();
即时添加列只能将列追加到表的末尾,因此 instant 不能与 after 或 first 修饰符一起使用。此外,该算法并不支持所有列类型或操作;如果操作不兼容,MySQL 会报错。
哪些操作支持即时列修改,请参阅 MySQL 文档。
DDL 锁
使用 MySQL 时,可以在列、索引或外键定义后链式调用 lock 修饰符,控制结构操作期间的表锁。MySQL 支持多种锁模式:none 允许并发读写,shared 允许并发读取但阻止写入,exclusive 阻止所有并发访问,default 则由 MySQL 选择最合适的模式:
$table->string('name')->lock('none');
$table->index('email')->lock('shared');
如果指定的锁模式与操作不兼容,MySQL 会报错。你可以将 lock 与 instant 修饰符结合,进一步优化结构变更:
$table->string('name')->instant()->lock('none');
修改列
change 方法可以修改现有列的类型和属性。例如,要增加一个 string 列的长度,可以将 name 列的长度从 25 改为 50:只需定义列的新状态,再调用 change 方法:
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->change();
});
修改列时,必须在列定义中明确包含所有需要保留的修饰符,否则对应属性会被移除。例如,要保留 unsigned、default 和 comment 属性,更改列时必须逐一调用这些修饰符:
Schema::table('users', function (Blueprint $table) {
$table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
});
change 方法不会更改列的索引。因此,可以在修改列时通过索引修饰符明确添加或删除索引:
// 添加索引……
$table->bigIncrements('id')->primary()->change();
// 删除索引……
$table->char('postal_code', 10)->unique(false)->change();
PostgreSQL 列修改
在 PostgreSQL 中修改列类型时,可以通过 using 修饰符指定转换现有值所用的表达式:
Schema::table('users', function (Blueprint $table) {
$table->date('birthday')->using('birthday::date')->change();
});
重命名列
要重命名列,可以使用数据库结构构建器的 renameColumn 方法:
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});
删除列
要删除列,可以使用数据库结构构建器的 dropColumn 方法:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
});
向 dropColumn 方法传入列名数组,即可一次删除多列:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['votes', 'avatar', 'location']);
});
可用命令别名
Laravel 提供了多种便捷方法,用于删除常见类型的列,具体如下:
| 命令 | 说明 |
|---|---|
$table->dropMorphs('morphable'); |
删除 morphable_type 和 morphable_id 列。 |
$table->dropRememberToken(); |
删除 remember_token 列。 |
$table->dropSoftDeletes(); |
删除 deleted_at 列。 |
$table->dropSoftDeletesTz(); |
dropSoftDeletes() 方法的别名。 |
$table->dropTimestamps(); |
删除 created_at 和 updated_at 列。 |
$table->dropTimestampsTz(); |
dropTimestamps() 方法的别名。 |
索引
创建索引
Laravel 的数据库结构构建器支持多种索引。以下示例创建一个 email 列,并要求其值唯一。在列定义后链式调用 unique 方法即可创建该索引:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->string('email')->unique();
});
你也可以先定义列,再创建索引。在数据库结构构建器的蓝图对象上调用 unique 方法,并传入需要唯一索引的列名即可:
$table->unique('email');
向索引方法传入列名数组,还可以创建复合索引:
$table->index(['account_id', 'created_at']);
创建索引时,Laravel 会根据表名、列名和索引类型自动生成索引名称。你也可以通过方法的第二个参数自行指定名称:
$table->unique('email', 'unique_email');
可用索引类型
Laravel 数据库结构构建器的蓝图类为各种受支持的索引提供了创建方法。每个索引方法都接受可选的第二个参数,用于指定索引名称。如果省略,就会根据表名、索引列名及索引类型生成名称。可用方法如下:
| 命令 | 说明 |
|---|---|
$table->primary('id'); |
添加主键。 |
$table->primary(['id', 'parent_id']); |
添加复合主键。 |
$table->unique('email'); |
添加唯一索引。 |
$table->index('state'); |
添加普通索引。 |
$table->fullText('body'); |
添加全文索引(MariaDB / MySQL / PostgreSQL)。 |
$table->fullText('body')->language('english'); |
添加指定语言的全文索引(PostgreSQL)。 |
$table->spatialIndex('location'); |
添加空间索引(SQLite 除外)。 |
$table->vectorIndex('embedding'); |
添加向量索引(MariaDB / PostgreSQL)。 |
在线创建索引
默认情况下,在大表上创建索引可能会锁表,在构建期间阻塞读写。使用 PostgreSQL 或 SQL Server 时,可以在索引定义后链式调用 online 方法,在不锁表的情况下创建索引,让应用在索引创建期间继续读写数据:
$table->unique('email')->online();
使用 PostgreSQL 时,这会为创建索引的语句添加 CONCURRENTLY 选项;使用 SQL Server 时,则添加 WITH (online = on) 选项。
使用 MySQL 时,可以在索引或外键定义后链式调用 inplace 修饰符,指定操作使用 INPLACE 算法:
$table->index('email')->inplace();
$table->foreign('user_id')->references('id')->on('users')->inplace();
inplace 可以与 lock 修饰符结合,控制操作期间的表锁:
$table->index('email')->inplace()->lock('none');
对外键操作使用 inplace 修饰符时,必须禁用外键检查。
哪些操作支持 INPLACE 算法及相应锁模式,请参阅 MySQL 文档。
重命名索引
要重命名索引,可以使用数据库结构构建器蓝图的 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->dropFullText('posts_body_fulltext'); |
删除「posts」表的全文索引。 |
$table->dropSpatialIndex('geo_location_spatialindex'); |
删除「geo」表的空间索引(SQLite 除外)。 |
$table->dropVectorIndex('documents_embedding_vectorindex'); |
删除「documents」表的向量索引。 |
如果向删除索引的方法传入列名数组,就会根据表名、列名和索引类型,按约定生成索引名称:
Schema::table('geo', function (Blueprint $table) {
$table->dropIndex(['state']); // 删除索引 'geo_state_index'
});
外键约束
Laravel 也支持创建外键约束,在数据库层面强制保证引用完整性。例如,以下代码定义 posts 表中的 user_id 列,使其引用 users 表的 id 列:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('posts', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
由于这种语法比较冗长,Laravel 还提供了基于约定的简洁方法,方便开发者使用。通过 foreignId 创建列时,上述示例可以改写为:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained();
});
foreignId 方法创建一个等效于 UNSIGNED BIGINT 的列,constrained 则按约定确定被引用的表和列。如果表名不符合 Laravel 的约定,可以手动将其传给 constrained。你还可以指定生成的索引名称:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained(
table: 'users', indexName: 'posts_user_id'
);
});
你也可以指定约束在「on delete」和「on update」时执行的操作:
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');
这些操作也提供了更清晰的替代语法:
| 方法 | 说明 |
|---|---|
$table->cascadeOnUpdate(); |
更新时级联更新。 |
$table->restrictOnUpdate(); |
限制更新。 |
$table->nullOnUpdate(); |
更新时将外键值设为 null。 |
$table->noActionOnUpdate(); |
更新时不执行操作。 |
$table->cascadeOnDelete(); |
删除时级联删除。 |
$table->restrictOnDelete(); |
限制删除。 |
$table->nullOnDelete(); |
删除时将外键值设为 null。 |
$table->noActionOnDelete(); |
存在子记录时阻止删除。 |
其他列修饰符必须在 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();
Schema::withoutForeignKeyConstraints(function () {
// 此闭包内禁用外键约束……
});
[!WARNING]
SQLite 默认禁用外键约束。使用 SQLite 时,请先在数据库配置中启用外键支持,再通过迁移创建外键约束。
事件
为方便使用,每个迁移操作都会派发一个事件。除 SchemaDumped 和 SchemaLoaded 外,下列事件都实现了 Illuminate\Contracts\Database\Events\MigrationEvent 接口:
| 类 | 说明 |
|---|---|
Illuminate\Database\Events\DatabaseRefreshed |
migrate:fresh 或 migrate:refresh 命令已执行完毕。 |
Illuminate\Database\Events\MigrationsStarted |
一批迁移即将执行。 |
Illuminate\Database\Events\MigrationsEnded |
一批迁移已执行完毕。 |
Illuminate\Database\Events\MigrationStarted |
单个迁移即将执行。 |
Illuminate\Database\Events\MigrationEnded |
单个迁移已执行完毕。 |
Illuminate\Database\Events\NoPendingMigrations |
迁移命令未发现待执行的迁移。 |
Illuminate\Database\Events\SchemaDumped |
数据库结构导出已完成。 |
Illuminate\Database\Events\SchemaLoaded |
已加载现有的数据库结构导出文件。 |
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu
推荐文章: