数据库迁移

未匹配的标注
本文档最新版为 12.x,旧版本可能放弃维护,推荐阅读最新版!

数据库:迁移

简介

迁移就像数据库的版本控制,让团队能够定义和共享应用的数据库结构。如果你曾经需要告诉队友,在拉取代码更改后手动向本地数据库添加一列,那么你就遇到了数据库迁移要解决的问题。

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');
});

可用列类型

数据库结构构建器的蓝图对象提供了多种方法,对应可添加到数据库表中的不同列类型。下方列出了所有可用方法:

布尔类型

字符串与文本类型

数值类型

日期与时间类型

二进制类型

对象与 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` | 已加载现有的数据库结构导出文件。 |

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/13.x/mi...

译文地址:https://learnku.com/docs/laravel/13.x/mi...

上一篇 下一篇
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:1
讨论数量: 0
发起讨论 查看所有版本


暂无话题~