迁移

简介

迁移就像是数据库的版本控制,让你的团队可以定义并共享应用的数据库结构定义。如果你曾经不得不在拉取队友的代码改动后,告诉对方手动给本地数据库加一列,那你已经亲身体会过迁移要解决的问题了。

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

# Dump the current database schema and prune all existing migrations...
php artisan schema:dump --prune

执行该命令时,Laravel 会把一份「schema」文件写入应用的 database/schema 目录。schema 文件名会与数据库连接相对应。这样,当你尝试迁移数据库且尚未执行过其他迁移时,Laravel 会先执行所用数据库连接对应的 schema 文件里的 SQL 语句。执行完 schema 文件的 SQL 后,Laravel 会继续执行未包含在 schema dump 中的剩余迁移。

如果应用的测试使用的数据库连接与本地开发常用的不同,应确保也用该连接 dump 了一份 schema 文件,这样测试才能构建出数据库。一般可以在 dump 完本地常用连接之后再 dump 一次:

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

应该把数据库 schema 文件提交到版本控制中,这样团队里其他新加入的开发者就能快速创建出应用的初始数据库结构。

注意

迁移压缩仅在 MariaDB、MySQL、PostgreSQL 和 SQLite 数据库上可用,并且依赖数据库的命令行客户端。

迁移结构

一个迁移类包含两个方法:up 和 down。up 方法用于向数据库添加新表、列或索引,而 down 方法应当反向撤销 up 方法所做的操作。

在这两个方法里,你都可以使用 Laravel 的 schema 构造器以富有表达力的方式创建和修改表。要了解 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 选项来回滚有限数量的迁移。比如下面的命令会回滚最近 5 个迁移:

php artisan migrate:rollback --step=5

你也可以通过给 rollback 命令提供 batch 选项来回滚指定的某一「批」迁移,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

# Refresh the database and run all database seeds...
php artisan migrate:refresh --seed

你可以通过给 refresh 命令提供 step 选项来回滚并重新迁移有限数量的迁移。比如下面的命令会回滚并重新迁移最近 5 个迁移:

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

创建表时,你可以使用 schema 构造器的任意列方法来定义表的列。

判断表 / 列是否存在

你可以使用 hasTable()、hasColumn() 和 hasIndex() 方法判断表、列或索引是否存在:

if (Schema::hasTable('users')) {
    // The "users" table exists...
}

if (Schema::hasColumn('users', 'email')) {
    // The "users" table exists and has an "email" column...
}

if (Schema::hasIndex('users', ['email'], 'unique')) {
    // The "users" table exists and has a unique index on the "email" column...
}

数据库连接与表选项

如果你想在非默认连接上执行 schema 操作,使用 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');
});

可用的列类型

schema 构造器的 blueprint 提供了各种方法,对应你可以添加到数据库表中的不同列类型。下面列出了所有可用的方法:

布尔类型 {#booleans-method-list}

boolean

字符串与文本类型 {#strings-and-texts-method-list}

char longText mediumText string text tinyText

数值类型 {#numbers--method-list}

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

日期与时间类型 {#dates-and-times-method-list}

dateTime dateTimeTz date time timeTz timestamp timestamps timestampsTz softDeletes softDeletesTz year

二进制类型 {#binaries-method-list}

binary

对象与 JSON 类型 {#object-and-jsons-method-list}

json jsonb

UUID 与 ULID 类型 {#uuids-and-ulids-method-list}

ulid ulidMorphs uuid uuidMorphs nullableUlidMorphs nullableUuidMorphs

空间类型 {#spatials-method-list}

geography geometry

关系类型 {#relationship-method-list}

foreignId foreignIdFor foreignUlid foreignUuid foreignUuidFor morphs nullableMorphs

特殊类型 {#specifics-method-list}

enum set macAddress ipAddress rememberToken vector

bigIncrements() {#column-method-bigIncrements}

bigIncrements() 方法创建一个自增的 UNSIGNED BIGINT(主键)等效列:

$table->bigIncrements('id');

bigInteger() {#column-method-bigInteger}

bigInteger() 方法创建一个 BIGINT 等效列:

$table->bigInteger('votes');

binary() {#column-method-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() {#column-method-boolean}

boolean() 方法创建一个 BOOLEAN 等效列:

$table->boolean('confirmed');

char() {#column-method-char}

char() 方法创建一个指定长度的 CHAR 等效列:

$table->char('name', length: 100);

dateTimeTz() {#column-method-dateTimeTz}

dateTimeTz() 方法创建一个带时区的 DATETIME 等效列,可选指定小数秒精度:

$table->dateTimeTz('created_at', precision: 0);

dateTime() {#column-method-dateTime}

dateTime() 方法创建一个 DATETIME 等效列,可选指定小数秒精度:

$table->dateTime('created_at', precision: 0);

date() {#column-method-date}

date() 方法创建一个 DATE 等效列:

$table->date('created_at');

decimal() {#column-method-decimal}

decimal() 方法创建一个 DECIMAL 等效列,需指定精度(总位数)和小数位(小数位数):

$table->decimal('amount', total: 8, places: 2);

double() {#column-method-double}

double() 方法创建一个 DOUBLE 等效列:

$table->double('amount');

enum() {#column-method-enum}

enum() 方法创建一个 ENUM 等效列,并指定有效值列表:

$table->enum('difficulty', ['easy', 'hard']);

当然,你也可以用 Enum::cases() 方法代替手动定义允许值数组:

use App\Enums\Difficulty;

$table->enum('difficulty', Difficulty::cases());

float() {#column-method-float}

float() 方法创建一个指定精度的 FLOAT 等效列:

$table->float('amount', precision: 53);

foreignId() {#column-method-foreignId}

foreignId() 方法创建一个 UNSIGNED BIGINT 等效列:

$table->foreignId('user_id');

foreignIdFor() {#column-method-foreignIdFor}

foreignIdFor() 方法为给定模型类添加一个 {column}_id 等效列。列类型会根据模型主键类型为 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26):

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

foreignUlid() {#column-method-foreignUlid}

foreignUlid() 方法创建一个 ULID 等效列:

$table->foreignUlid('user_id');

foreignUuid() {#column-method-foreignUuid}

foreignUuid() 方法创建一个 UUID 等效列:

$table->foreignUuid('user_id');

foreignUuidFor() {#column-method-foreignUuidFor}

foreignUuidFor() 方法为给定模型类添加一个 {column}_id UUID 等效列:

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

geography() {#column-method-geography}

geography() 方法创建一个 GEOGRAPHY 等效列,需指定空间类型和 SRID(空间参考系统标识符):

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

信息

空间类型的支持取决于你的数据库驱动,请参阅数据库的相关文档。如果应用使用 PostgreSQL 数据库,必须先安装 PostGIS 扩展才能使用 geography() 方法。

geometry() {#column-method-geometry}

geometry() 方法创建一个 GEOMETRY 等效列,需指定空间类型和 SRID(空间参考系统标识符):

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

信息

空间类型的支持取决于你的数据库驱动,请参阅数据库的相关文档。如果应用使用 PostgreSQL 数据库,必须先安装 PostGIS 扩展才能使用 geometry() 方法。

id() {#column-method-id}

id() 方法是 bigIncrements() 方法的别名。默认情况下,该方法会创建一个 id 列;不过你也可以传入列名来指定其他名称:

$table->id();

increments() {#column-method-increments}

increments() 方法创建一个自增的 UNSIGNED INTEGER 等效列作为主键:

$table->increments('id');

integer() {#column-method-integer}

integer() 方法创建一个 INTEGER 等效列:

$table->integer('votes');

ipAddress() {#column-method-ipAddress}

ipAddress() 方法创建一个 VARCHAR 等效列:

$table->ipAddress('visitor');

使用 PostgreSQL 时,会创建一个 INET 列。

json() {#column-method-json}

json() 方法创建一个 JSON 等效列:

$table->json('options');

使用 SQLite 时,会创建一个 TEXT 列。

jsonb() {#column-method-jsonb}

jsonb() 方法创建一个 JSONB 等效列:

$table->jsonb('options');

使用 SQLite 时,会创建一个 TEXT 列。

longText() {#column-method-longText}

longText() 方法创建一个 LONGTEXT 等效列:

$table->longText('description');

使用 MySQL 或 MariaDB 时,可以给该列应用 binary 字符集,从而创建一个 LONGBLOB 等效列:

$table->longText('data')->charset('binary'); // LONGBLOB

macAddress() {#column-method-macAddress}

macAddress() 方法创建一个用于存放 MAC 地址的列。某些数据库系统(如 PostgreSQL)有专门用于此类数据的列类型,其他数据库系统则会使用字符串等效列:

$table->macAddress('device');

mediumIncrements() {#column-method-mediumIncrements}

mediumIncrements() 方法创建一个自增的 UNSIGNED MEDIUMINT 等效列作为主键:

$table->mediumIncrements('id');

mediumInteger() {#column-method-mediumInteger}

mediumInteger() 方法创建一个 MEDIUMINT 等效列:

$table->mediumInteger('votes');

mediumText() {#column-method-mediumText}

mediumText() 方法创建一个 MEDIUMTEXT 等效列:

$table->mediumText('description');

使用 MySQL 或 MariaDB 时,可以给该列应用 binary 字符集,从而创建一个 MEDIUMBLOB 等效列:

$table->mediumText('data')->charset('binary'); // MEDIUMBLOB

morphs() {#column-method-morphs}

morphs() 方法是一个便捷方法,会添加一个 {column}_type VARCHAR 等效列和一个 {column}_id 等效列。{column}_id 的列类型会根据模型主键类型为 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26)。

该方法用于定义多态 Eloquent 关系所需的列。下面这个例子会创建 taggable_type 和 taggable_id 两列:

$table->morphs('taggable');

nullableMorphs() {#column-method-nullableMorphs}

该方法与 morphs 方法类似,不过创建的列是「可空」的:

$table->nullableMorphs('taggable');

nullableUlidMorphs() {#column-method-nullableUlidMorphs}

该方法与 ulidMorphs 方法类似,不过创建的列是「可空」的:

$table->nullableUlidMorphs('taggable');

nullableUuidMorphs() {#column-method-nullableUuidMorphs}

该方法与 uuidMorphs 方法类似,不过创建的列是「可空」的:

$table->nullableUuidMorphs('taggable');

rememberToken() {#column-method-rememberToken}

rememberToken() 方法创建一个可空的 VARCHAR(100) 等效列,用于存放当前的「记住我」认证令牌:

$table->rememberToken();

set() {#column-method-set}

set() 方法创建一个 SET 等效列,并指定有效值列表:

$table->set('flavors', ['strawberry', 'vanilla']);

smallIncrements() {#column-method-smallIncrements}

smallIncrements() 方法创建一个自增的 UNSIGNED SMALLINT 等效列作为主键:

$table->smallIncrements('id');

smallInteger() {#column-method-smallInteger}

smallInteger() 方法创建一个 SMALLINT 等效列:

$table->smallInteger('votes');

softDeletesTz() {#column-method-softDeletesTz}

softDeletesTz() 方法添加一个可空的 deleted_at 带时区 TIMESTAMP 等效列,可选指定小数秒精度。该列用于存放 Eloquent「软删除」功能所需的 deleted_at 时间戳:

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

softDeletes() {#column-method-softDeletes}

softDeletes() 方法添加一个可空的 deleted_at TIMESTAMP 等效列,可选指定小数秒精度。该列用于存放 Eloquent「软删除」功能所需的 deleted_at 时间戳:

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

string() {#column-method-string}

string() 方法创建一个指定长度的 VARCHAR 等效列:

$table->string('name', length: 100);

text() {#column-method-text}

text() 方法创建一个 TEXT 等效列:

$table->text('description');

使用 MySQL 或 MariaDB 时,可以给该列应用 binary 字符集,从而创建一个 BLOB 等效列:

$table->text('data')->charset('binary'); // BLOB

timeTz() {#column-method-timeTz}

timeTz() 方法创建一个带时区的 TIME 等效列,可选指定小数秒精度:

$table->timeTz('sunrise', precision: 0);

time() {#column-method-time}

time() 方法创建一个 TIME 等效列,可选指定小数秒精度:

$table->time('sunrise', precision: 0);

timestampTz() {#column-method-timestampTz}

timestampTz() 方法创建一个带时区的 TIMESTAMP 等效列,可选指定小数秒精度:

$table->timestampTz('added_at', precision: 0);

timestamp() {#column-method-timestamp}

timestamp() 方法创建一个 TIMESTAMP 等效列,可选指定小数秒精度:

$table->timestamp('added_at', precision: 0);

timestampsTz() {#column-method-timestampsTz}

timestampsTz() 方法创建 created_at 和 updated_at 两个带时区的 TIMESTAMP 等效列,可选指定小数秒精度:

$table->timestampsTz(precision: 0);

timestamps() {#column-method-timestamps}

timestamps() 方法创建 created_at 和 updated_at 两个 TIMESTAMP 等效列,可选指定小数秒精度:

$table->timestamps(precision: 0);

tinyIncrements() {#column-method-tinyIncrements}

tinyIncrements() 方法创建一个自增的 UNSIGNED TINYINT 等效列作为主键:

$table->tinyIncrements('id');

tinyInteger() {#column-method-tinyInteger}

tinyInteger() 方法创建一个 TINYINT 等效列:

$table->tinyInteger('votes');

tinyText() {#column-method-tinyText}

tinyText() 方法创建一个 TINYTEXT 等效列:

$table->tinyText('notes');

使用 MySQL 或 MariaDB 时,可以给该列应用 binary 字符集,从而创建一个 TINYBLOB 等效列:

$table->tinyText('data')->charset('binary'); // TINYBLOB

unsignedBigInteger() {#column-method-unsignedBigInteger}

unsignedBigInteger() 方法创建一个 UNSIGNED BIGINT 等效列:

$table->unsignedBigInteger('votes');

unsignedInteger() {#column-method-unsignedInteger}

unsignedInteger() 方法创建一个 UNSIGNED INTEGER 等效列:

$table->unsignedInteger('votes');

unsignedMediumInteger() {#column-method-unsignedMediumInteger}

unsignedMediumInteger() 方法创建一个 UNSIGNED MEDIUMINT 等效列:

$table->unsignedMediumInteger('votes');

unsignedSmallInteger() {#column-method-unsignedSmallInteger}

unsignedSmallInteger() 方法创建一个 UNSIGNED SMALLINT 等效列:

$table->unsignedSmallInteger('votes');

unsignedTinyInteger() {#column-method-unsignedTinyInteger}

unsignedTinyInteger() 方法创建一个 UNSIGNED TINYINT 等效列:

$table->unsignedTinyInteger('votes');

ulidMorphs() {#column-method-ulidMorphs}

ulidMorphs() 方法是一个便捷方法,会添加一个 {column}_type VARCHAR 等效列和一个 {column}_id CHAR(26) 等效列。

该方法用于定义使用 ULID 标识符的多态 Eloquent 关系所需的列。下面这个例子会创建 taggable_type 和 taggable_id 两列:

$table->ulidMorphs('taggable');

uuidMorphs() {#column-method-uuidMorphs}

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

该方法用于定义使用 UUID 标识符的多态 Eloquent 关系所需的列。下面这个例子会创建 taggable_type 和 taggable_id 两列:

$table->uuidMorphs('taggable');

ulid() {#column-method-ulid}

ulid() 方法创建一个 ULID 等效列:

$table->ulid('id');

uuid() {#column-method-uuid}

uuid() 方法创建一个 UUID 等效列:

$table->uuid('id');

vector() {#column-method-vector}

vector() 方法创建一个 vector 等效列:

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

使用 PostgreSQL 时,创建 vector 列之前必须先加载 pgvector 扩展:

Schema::ensureVectorExtensionExists();

year() {#column-method-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() 使用 instant 操作添加或修改列(MySQL)。
->invisible() 让列对 SELECT * 查询「不可见」(MariaDB / MySQL)。
->lock($mode) 指定列操作的锁模式(MySQL)。
->nullable($value = true) 允许向列插入 NULL 值。
->storedAs($expression) 创建一个存储型生成列(MariaDB / MySQL / PostgreSQL / SQLite)。
->unsigned() 把 INTEGER 列设为 UNSIGNED(MariaDB / MySQL)。
->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() 方法在 schema 中已有列之后添加列:

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

Instant 列操作

使用 MySQL 时,可以在列定义上链式调用 instant 修饰符,表示该列应使用 MySQL 的「instant」算法添加或修改。该算法允许某些 schema 变更无需完整重建表即可完成,无论表多大都几乎是瞬时的:

$table->string('name')->nullable()->instant();

instant 列添加只能把列追加到表的末尾,因此 instant 修饰符不能与 after 或 first 修饰符组合使用。此外,该算法并不支持所有列类型或操作。如果请求的操作不兼容,MySQL 会抛出错误。

请参阅 MySQL 文档来确定哪些操作与 instant 列修改兼容。

DDL 锁定

使用 MySQL 时,可以在列、索引或外键定义上链式调用 lock 修饰符,以控制 schema 操作期间的表锁。MySQL 支持几种锁模式:none 允许并发读写,shared 允许并发读但阻塞写,exclusive 阻塞所有并发访问,default 则让 MySQL 自行选择最合适的模式:

$table->string('name')->lock('none');

$table->index('email')->lock('shared');

如果请求的锁模式与操作不兼容,MySQL 会抛出错误。lock 修饰符可以与 instant 修饰符组合使用,以进一步优化 schema 变更:

$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();

重命名列

要重命名列,可以使用 schema 构造器提供的 renameColumn() 方法:

Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});

删除列

要删除列,可以使用 schema 构造器上的 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 的 schema 构造器支持多种类型的索引。下面这个例子创建一个新的 email 列,并指定其值必须唯一。要创建索引,可以在列定义上链式调用 unique() 方法:

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

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->unique();
});

或者,你也可以在定义完列之后再创建索引。为此,应在 schema 构造器的 blueprint 上调用 unique() 方法。该方法接收应建立唯一索引的列名:

$table->unique('email');

你甚至可以给索引方法传入一个列数组来创建复合(组合)索引:

$table->index(['account_id', 'created_at']);

创建索引时,Laravel 会根据表名、列名和索引类型自动生成索引名,不过你也可以给方法传入第二个参数来自行指定索引名:

$table->unique('email', 'unique_email');

可用的索引类型

Laravel 的 schema 构造器 blueprint 类为 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 除外)。

在线创建索引

默认情况下,在大表上创建索引可能会锁表,并在索引构建期间阻塞读写。使用 PostgreSQL 或 SQL Server 时,可以在索引定义上链式调用 online() 方法,在不锁表的情况下创建索引,让应用在索引创建期间继续读写数据:

$table->string('email')->unique()->online();

使用 PostgreSQL 时,这会给索引创建语句加上 CONCURRENTLY 选项。使用 SQL Server 时,则会加上 WITH (online = on) 选项。

重命名索引

要重命名索引,可以使用 schema 构造器 blueprint 提供的 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 除外)。

如果你给删除索引的方法传入一个列数组,约定的索引名会根据表名、列和索引类型生成:

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

针对这些行为,Laravel 还提供了一种更具表达力的替代语法:

方法 说明
$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 时,请确保在迁移中尝试创建外键之前,先在数据库配置中启用外键支持。

事件

为方便起见,每个迁移操作都会派发一个事件。下面所有事件都继承自基础的 Illuminate\Database\Events\MigrationEvent 类:

类 说明
Illuminate\Database\Events\DatabaseRefreshed 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 数据库 schema dump 已完成。
Illuminate\Database\Events\SchemaLoaded 已有的数据库 schema dump 已加载。