简介

迁移就像是数据库的版本控制,能让你的团队定义并共享应用的数据库结构定义。如果你曾需要在从源码控制拉取变更后,手动让队友在本地数据库结构中添加列,那么你已遇到过数据库迁移所解决的问题。

Laravel 的 Schema 外观 提供了数据库无关的支持,可用于在 Laravel 支持的所有数据库系统中创建和操作表。通常,迁移会使用此外观来创建和修改数据库表与列。

生成迁移

你可以使用 make:migration Artisan 命令 来生成数据库迁移。新的迁移将放置在你的 database/migrations 目录中。每个迁移文件名都包含一个时间戳,以便 Laravel 确定迁移的顺序:

php artisan make:migration create_flights_table

Laravel 会依据迁移名称,尝试推测表名以及该迁移是否用于创建新表。若 Laravel 能从迁移名称中确定表名,就会在生成的迁移文件中预填指定的表。否则,你可以在迁移文件中手动指定表名。

如果你想为生成的迁移指定自定义路径,在执行 make:migration 命令时可使用 --path 选项。给定的路径应相对于应用的基路径。

可通过 存根发布 来自定义迁移存根。

压缩迁移

在构建应用的过程中,随着时间推移,你可能会积累越来越多的迁移。这会导致 database/migrations 目录变得臃肿,可能会包含数百个迁移。若你愿意,可将这些迁移“压缩”成一个单独的 SQL 文件。要开始此操作,执行 schema:dump 命令:

php artisan schema:dump

# 导出当前数据库结构,并删除所有现有迁移……
php artisan schema:dump --prune

执行此命令后,Laravel 会将一个“结构”文件写入应用的 database/schema 目录。结构文件的名称将与数据库连接相对应。现在,当你尝试对数据库执行迁移且尚未执行其他迁移时,Laravel 会先执行你正在使用的数据库连接对应的结构文件中的 SQL 语句。执行完结构文件的 SQL 语句后,Laravel 会执行不在结构导出范围内的剩余迁移。

如果应用的测试使用的数据库连接与你在本地开发时通常使用的连接不同,你应确保使用该数据库连接导出了结构文件,以便测试能够构建数据库。你可能希望在导出本地开发常用的数据库连接之后执行此操作:

php artisan schema:dump
php artisan schema:dump --database=testing --prune

你应将数据库结构文件提交到源码控制,以便团队中的其他新成员能快速创建应用的初始数据库结构。

迁移压缩功能仅适用于 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
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::drop('flights');
    }
};

设置迁移连接

如果你的迁移要与应用默认数据库连接之外的数据库连接交互,应设置迁移的 $connection 属性:

/**
 * The database connection that should be used by the migration.
 *
 * @var string
 */
protected $connection = 'pgsql';

/**
 * Run the migrations.
 */
public function up(): void
{
    // ...
}

跳过迁移

有时,某个迁移可能旨在支持尚未启用的功能,你不希望它现在运行。这种情况下,可在迁移中定义 shouldRun 方法。若 shouldRun 方法返回 false,则该迁移将被跳过:

use App\Models\Flight;
use Laravel\Pennant\Feature;

/**
 * Determine if this migration should run.
 */
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

要使用此功能,应用必须使用 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 选项,回滚特定的一批迁移,其中 batch 选项对应应用 migrations 数据库表中的批次值。例如,以下命令将回滚第三批中的所有迁移:

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

migrate:fresh 命令会删除所有数据库表,无论其前缀是什么。当在与其他应用共享的数据库上进行开发时,使用该命令应格外谨慎。

表

创建表

要创建新的数据库表,请使用 Schema 门面上的 create 方法。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 方法一样,table 方法接收两个参数:表名和一个闭包,该闭包接收一个 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 方法一样,table 方法接收两个参数:表名和一个闭包,该闭包接收一个 Illuminate\Database\Schema\Blueprint 实例,可用于向表中添加列:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->integer('votes');
});

可用的列类型

架构构建器的蓝图提供了多种方法,对应您可以添加到数据库表中的不同类型列。每种可用方法均列于下表:

布尔类型

boolean

字符串和文本类型

char longText mediumText string text tinyText

数值类型

bigIncrements bigInteger decimal double float id increments integer mediumIncrements mediumInteger smallIncrements smallInteger tinyIncrements tinyInteger unsignedBigInteger unsignedInteger unsignedMediumInteger unsignedSmallInteger unsignedTinyInteger

日期和时间类型

dateTime dateTimeTz date time timeTz timestamp timestamps timestampsTz softDeletes softDeletesTz year

二进制类型

binary

对象和JSON类型

json jsonb

UUID和ULID类型

ulid ulidMorphs uuid uuidMorphs nullableUlidMorphs nullableUuidMorphs

空间类型

geography geometry

关系类型

foreignId foreignIdFor foreignUlid foreignUuid foreignUuidFor morphs nullableMorphs

特殊类型

enum set macAddress ipAddress rememberToken vector

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 方法用于为指定的模型类添加等效于 {column}_id 的 UUID 列:

$table->foreignUuidFor(User::class);

geography()

geography 方法用于创建等效于 GEOGRAPHY 的列,可指定空间类型和 SRID(空间参照系标识符):

$table->geography('coordinates', subtype: 'point', srid: 4326);

空间类型的支持取决于所使用的数据库驱动。请参阅你的数据库文档。如果你的应用使用的是 PostgreSQL 数据库,在使用 geography 方法之前,必须先安装 PostGIS 扩展。

geometry()

geometry 方法用于创建等效于 GEOMETRY 的列,可指定空间类型和 SRID(空间参照系标识符):

$table->geometry('positions', subtype: 'point', srid: 0);

空间类型的支持取决于所使用的数据库驱动。请参阅你的数据库文档。如果你的应用使用的是 PostgreSQL 数据库,在使用 geometry 方法之前,必须先安装 PostGIS 扩展。

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 方法是一种便捷方法,用于添加等效于 {column}_type 的 VARCHAR 列,以及等效于 {column}_id 的列。其中,{column}_id 列的类型取决于模型主键类型,可能为 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26)。

该方法旨在为多态的 Eloquent 关系 定义所需的列。在下面的示例中,会创建 taggable_type 和 taggable_id 列:

$table->morphs('taggable');

nullableMorphs()

该方法与 morphs 方法类似;不过,所创建的列会设置为“可为空”:

$table->nullableMorphs('taggable');

nullableUlidMorphs()

该方法与 ulidMorphs 方法类似;不过,所创建的列会设置为“可为空”:

$table->nullableUlidMorphs('taggable');

nullableUuidMorphs()

该方法与 uuidMorphs 方法类似;不过,所创建的列会设置为“可为空”:

$table->nullableUuidMorphs('taggable');

rememberToken()

rememberToken 方法用于创建可为空且等效于 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 方法会添加一个可为空的 deleted_at TIMESTAMP(带时区)等效列,并支持可选的小数秒精度。该列用于存储 Eloquent “软删除”功能所需的 deleted_at 时间戳:

$table->softDeletesTz('deleted_at', precision: 0);

softDeletes()

softDeletes 方法会添加一个可为空的 deleted_at TIMESTAMP 等效列,并支持可选的小数秒精度。该列用于存储 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 方法是一个便捷方法,用于添加一个 {column}_type VARCHAR 等效列以及一个 {column}_id CHAR(26) 等效列。

当定义使用 ULID 标识符的多态 Eloquent 关系所需的列时,应使用此方法。在下面的示例中,将创建 taggable_type 和 taggable_id 列:

$table->ulidMorphs('taggable');

uuidMorphs()

uuidMorphs 方法是一个便捷方法,用于添加一个 {column}_type VARCHAR 等效列以及一个 {column}_id CHAR(36) 等效列。

当定义使用 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 及更高版本中,支持 vector 列。当使用 PostgreSQL 时,必须先加载 pgvector,才能创建 vector 列:

Schema::ensureVectorExtensionExists();

为了加快 向量相似性查询的速度,可以为该列添加向量索引。在 vector 列上调用 index 方法,会使用余弦距离创建一个向量索引:

$table->vector('embedding', dimensions: 1536)->index();

year()

year 方法会创建一个 YEAR 等效列:

$table->year('birth_year');

列修饰符

除了上述列类型外,在向数据库表添加列时,还可以使用多种列“修饰符”。例如,若要让列“可为空”,可以使用 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
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            $table->json('movies')->default(new Expression('(JSON_ARRAY())'));
            $table->timestamps();
        });
    }
};

对默认表达式的支持取决于你的数据库驱动、数据库版本以及字段类型。请参阅你的数据库文档。

列顺序

当使用 MariaDB 或 MySQL 数据库时,可以使用 after 方法在架构中的现有列之后添加列:

$table->after('password', function (Blueprint $table) {
    $table->string('address_line1');
    $table->string('address_line2');
    $table->string('city');
});

即时列操作

使用 MySQL 时,可以在列定义中链式调用 instant 修饰符,以指示应使用 MySQL 的“即时”算法来添加或修改列。该算法允许执行某些架构更改,而无需重建整个表,无论表的大小如何,都能近乎瞬时完成:

$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 列的大小。为了演示 change 方法的使用,让我们将 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 方法不会更改列的索引。因此,你可以在修改列时使用索引修饰符来显式添加或删除索引:

// Add an index...
$table->bigIncrements('id')->primary()->change();

// Drop an index...
$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 的模式构建器蓝图类提供了用于创建 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 修饰符时,必须禁用外键检查。

请参阅MySQL 文档,以确定哪些操作支持 INPLACE 算法和锁模式。

重命名索引

若要重命名索引,可以使用模式构建器蓝图提供的 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']); // Drops index '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 () {
    // Constraints disabled within this closure...
});

SQLite 默认会禁用外键约束。当使用 SQLite 时,在迁移中尝试创建外键之前,请务必在数据库配置中启用外键支持。

事件

为方便起见,每次迁移操作都会派发一个事件。除 SchemaDumped 和 SchemaLoaded 外,以下所有事件都实现了 Illuminate\Contracts\Database\Events\MigrationEvent 接口:

类说明
Illuminate\Database\Events\DatabaseRefreshedmigrate: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已有的数据库结构转储已加载。