数据库:迁移
简介
迁移就像是数据库的版本控制,允许你的团队定义和共享应用的数据库模式定义。如果你曾经不得不告诉队友在从源代码控制拉取你的更改后手动向其本地数据库模式添加列,那么你就遇到了数据库迁移所解决的问题。
Laravel 的 Schema 门面为在 Laravel 支持的所有数据库系统中创建和操作表提供了与数据库无关的支持。通常,迁移将使用此门面来创建和修改数据库表和列。
生成迁移
你可以使用 make:migration Artisan 命令生成数据库迁移。新的迁移将放置在 database/migrations 目录中。每个迁移文件名都包含一个时间戳,使 Laravel 能够确定迁移的顺序:
php artisan make:migration create_flights_tableLaravel 将使用迁移的名称来尝试猜测表的名称以及迁移是否将创建新表。如果 Laravel 能够从迁移名称中确定表名,Laravel 将使用指定的表预先填充生成的迁移文件。否则,你可以直接在迁移文件中手动指定表。
如果你希望为生成的迁移指定自定义路径,可以在执行 make:migration 命令时使用 --path 选项。给定的路径应相对于应用的基础路径。
NOTE
迁移模板可以使用模板发布进行自定义。
压缩迁移
随着你构建应用,迁移文件会随着时间的推移越来越多。这可能导致 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 目录。模式文件的名称将对应于数据库连接。现在,当你尝试迁移数据库且没有其他迁移已执行时,Laravel 将首先执行你正在使用的数据库连接的模式文件中的 SQL 语句。执行模式文件的 SQL 语句后,Laravel 将执行任何不属于模式转储的剩余迁移。
如果你的应用的测试使用与本地开发时不同的数据库连接,你应确保已使用该数据库连接转储了模式文件,以便你的测试能够构建数据库。你可能希望在转储本地开发时通常使用的数据库连接后执行此操作:
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
{
/**
* 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 --isolatedWARNING
要使用此功能,你的应用必须使用 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 --pretendmigrate: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 选项来回滚并重新迁移有限数量的迁移。例如,以下命令将回滚并重新迁移最后五个迁移:
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=adminWARNING
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')) {
// 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...
}数据库连接和表选项
如果要在非应用默认连接的数据库连接上执行模式操作,请使用 connection 方法:
Schema::connection('sqlite')->create('users', function (Blueprint $table) {
$table->id();
});此外,还有一些其他属性和方法可用于定义表创建的其他方面。engine 属性可用于在使用 MariaDB 或 MySQL 时指定表的存储引擎:
Schema::create('users', function (Blueprint $table) {
$table->engine('InnoDB');
// ...
});charset 和 collation 属性可用于在使用 MariaDB 或 MySQL 时指定创建表的字符集和排序规则:
Schema::create('users', function (Blueprint $table) {
$table->charset('utf8mb4');
$table->collation('utf8mb4_unicode_ci');
// ...
});temporary 方法可用于指示表应为"temporary"。临时表仅对当前连接的数据库会话可见,并在连接关闭时自动删除:
Schema::create('calculations', function (Blueprint $table) {
$table->temporary();
// ...
});如果你想向数据库表添加"comment",可以在表实例上调用 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');
});可用的列类型
模式构建器蓝图提供了多种方法,对应于你可以添加到数据库表中的不同类型列。下面列出了每种可用方法:
布尔类型
字符串和文本类型
数值类型
bigIncrementsbigIntegerdecimaldoublefloatidincrementsintegermediumIncrementsmediumIntegersmallIncrementssmallIntegertinyIncrementstinyIntegerunsignedBigIntegerunsignedIntegerunsignedMediumIntegerunsignedSmallIntegerunsignedTinyInteger
日期和时间类型
二进制类型
对象和 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 方法为给定模型类添加一个 {column}_id UUID 等效列:
$table->foreignUuidFor(User::class);geography()
geography 方法创建一个具有给定空间类型和 SRID(空间参考系标识符)的 GEOGRAPHY 等效列:
$table->geography('coordinates', subtype: 'point', srid: 4326);NOTE
对空间类型的支持取决于你的数据库驱动。请参考你的数据库文档。如果你的应用使用 PostgreSQL 数据库,则在使用 geography 方法之前必须安装 PostGIS 扩展。
geometry()
geometry 方法创建一个具有给定空间类型和 SRID(空间参考系标识符)的 GEOMETRY 等效列:
$table->geometry('positions', subtype: 'point', srid: 0);NOTE
对空间类型的支持取决于你的数据库驱动。请参考你的数据库文档。如果你的应用使用 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'); // LONGBLOBmacAddress()
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'); // MEDIUMBLOBmorphs()
morphs 方法是一个便利方法,它添加一个 {column}_type VARCHAR 等效列和一个 {column}_id 等效列。{column}_id 的列类型将为 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26),具体取决于模型键类型。
此方法旨在用于定义多态 Eloquent 关系所需的列。在以下示例中,将创建 taggable_type 和 taggable_id 列:
$table->morphs('taggable');nullableMorphs()
该方法类似于 morphs 方法;但是,创建的列将是"nullable":
$table->nullableMorphs('taggable');nullableUlidMorphs()
该方法类似于 ulidMorphs 方法;但是,创建的列将是"nullable":
$table->nullableUlidMorphs('taggable');nullableUuidMorphs()
该方法类似于 uuidMorphs 方法;但是,创建的列将是"nullable":
$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'); // BLOBtimeTz()
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'); // TINYBLOBunsignedBigInteger()
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: 100);使用 PostgreSQL 时,必须加载 pgvector 扩展后才能创建 vector 列:
Schema::ensureVectorExtensionExists();year()
year 方法创建一个 YEAR 等效列:
$table->year('birth_year');列修饰符
除了上面列出的列类型,还有几个列"修饰符"可用于向数据库表添加列时使用。例如,要使列"nullable",可以使用 nullable 方法:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable();
});下表包含所有可用的列修饰符。此列表不包括索引修饰符:
| 修饰符 | 描述 |
|---|---|
->after('column') | 将列放置在另一列"after"(MariaDB / MySQL)。 |
->autoIncrement() | 将 INTEGER 列设置为自增(主键)。 |
->charset('utf8mb4') | 为列指定字符集(MariaDB / MySQL)。 |
->collation('utf8mb4_unicode_ci') | 为列指定排序规则。 |
->comment('my comment') | 为列添加注释(MariaDB / MySQL / PostgreSQL)。 |
->default($value) | 为列指定"default"值。 |
->first() | 将列放置在表的"first"位置(MariaDB / MySQL)。 |
->from($integer) | 设置自增字段的起始值(MariaDB / MySQL / PostgreSQL)。 |
->instant() | 使用 MySQL 的 instan 操作添加或修改列(MySQL)。 |
->invisible() | 使列对 SELECT * 查询"invisible"(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();
});
}
};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 列的大小。要查看 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();重命名列
要重命名列,你可以使用模式构建器提供的 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 除外)。 |
在线索引创建
默认情况下,在大型表上创建索引会锁定表,并在构建索引时阻止读取或写入。使用 PostgreSQL 或 SQL Server 时,你可以将 online 方法链式添加到索引定义上,以在不锁定表的情况下创建索引,允许你的应用在索引创建期间继续读取和写入数据:
$table->string('email')->unique()->online();使用 PostgreSQL 时,这会向索引创建语句添加 CONCURRENTLY 选项。使用 SQL Server 时,这会添加 WITH (online = on) 选项。
重命名索引
要重命名索引,你可以使用模式构建器蓝图提供的 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 表上定义一个引用 users 表的 id 列的 user_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...
});WARNING
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 | 数据库模式转储已完成。 |
Illuminate\Database\Events\SchemaLoaded | 现有数据库模式转储已加载。 |