- 概述
- 迁移表
- 创建迁移
- 使用命令行工具
- 手动创建
- 迁移文件结构
- 基本结构
- 命名规范
- 运行迁移
- 运行所有迁移
- 运行指定迁移
- 检查迁移状态
- 回滚迁移
- 回滚最后一次迁移
- 回滚指定批次
- 回滚所有迁移
- 回滚并重新运行
- Schema 构建器
- 创建表
- 修改表
- 删除表
- 重命名表
- 列类型
- 字符串类型
- 整数类型
- 浮点数类型
- 日期时间类型
- 其他类型
- 列修饰符
- NULL / NOT NULL
- 默认值
- 自增
- 唯一索引
- 索引
- 外键
- 注释
- 位置
- 修改列
- 修改列类型
- 重命名列
- 删除列
- 索引操作
- 添加索引
- 删除索引
- 外键操作
- 添加外键
- 删除外键
- 数据迁移
- 完整示例
- 创建用户表
- 创建文章表(带外键)
- 修改表结构
- 最佳实践
- 1. 保持迁移文件小且专注
- 2. 总是实现 down 方法
- 3. 使用描述性的迁移名称
- 4. 不要在生产环境直接修改迁移
- 5. 测试迁移和回滚
- 运行迁移
- 回滚测试
- 再次运行
- 6. 使用事务(如果数据库支持)
- 常见问题
- Q: 迁移文件应该放在哪里?
- Q: 如何重置数据库?
- Q: 迁移失败怎么办?
- Q: 可以在迁移中使用模型吗?
- Q: 如何查看迁移状态?
- 相关文档
数据库迁移
最后更新: 2026-01-27 11:02:47数据库迁移
数据库迁移提供了一种版本化数据库结构的方式,让团队能够协作开发并保持数据库结构的一致性。
概述
迁移文件记录了数据库结构的变更历史,包括:
- 创建表
- 修改表结构
- 删除表
- 添加/删除索引
- 添加/删除外键
迁移表
框架会自动创建 migrations 表来跟踪已执行的迁移:
CREATE TABLE migrations (
id INT AUTO_INCREMENT PRIMARY KEY,
migration VARCHAR(255) NOT NULL,
batch INT NOT NULL
);
创建迁移
使用命令行工具
php console make:migration create_users_table
这会创建一个新的迁移文件,文件名格式:YYYYMMDDHHMMSS_create_users_table.php
手动创建
在 database/migrations/ 目录下创建迁移文件:
<?php
declare(strict_types=1);
namespace Database\Migrations;
use Unicode\Framework\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up(): void
{
$this->schema()->create('users', function($table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
}
public function down(): void
{
$this->schema()->drop('users');
}
}
迁移文件结构
基本结构
<?php
declare(strict_types=1);
namespace Database\Migrations;
use Unicode\Framework\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/
* 执行迁移(创建表或修改结构)
*/
public function up(): void
{
// 迁移逻辑
}
/
* 回滚迁移(撤销 up 中的操作)
*/
public function down(): void
{
// 回滚逻辑
}
}
命名规范
CreateUsersTablecreate_users_table.php运行迁移
运行所有迁移
php console migrate
运行指定迁移
php console migrate --migration=CreateUsersTable
检查迁移状态
php console migrate:status
回滚迁移
回滚最后一次迁移
php console migrate:rollback
回滚指定批次
php console migrate:rollback --batch=2
回滚所有迁移
php console migrate:reset
回滚并重新运行
php console migrate:refresh
Schema 构建器
创建表
public function up(): void
{
$this->schema()->create('users', function($table) {
$table->id(); // BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
$table->string('name'); // VARCHAR(255)
$table->string('email')->unique(); // VARCHAR(255) UNIQUE
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
}
修改表
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->string('phone')->nullable()->after('email');
$table->index('email');
});
}
删除表
public function down(): void
{
$this->schema()->drop('users');
}
// 或检查表是否存在
public function down(): void
{
$this->schema()->dropIfExists('users');
}
重命名表
public function up(): void
{
$this->schema()->rename('old_table_name', 'new_table_name');
}
列类型
字符串类型
$table->string('name'); // VARCHAR(255)
$table->string('name', 100); // VARCHAR(100)
$table->text('description'); // TEXT
$table->longText('content'); // LONGTEXT
$table->char('code', 10); // CHAR(10)
整数类型
$table->integer('age'); // INT
$table->bigInteger('id'); // BIGINT
$table->smallInteger('status'); // SMALLINT
$table->tinyInteger('flag'); // TINYINT
$table->unsignedInteger('count'); // UNSIGNED INT
$table->unsignedBigInteger('user_id'); // UNSIGNED BIGINT
浮点数类型
$table->float('price'); // FLOAT
$table->double('amount'); // DOUBLE
$table->decimal('total', 10, 2); // DECIMAL(10, 2)
日期时间类型
$table->date('birthday'); // DATE
$table->time('start_time'); // TIME
$table->dateTime('created_at'); // DATETIME
$table->timestamp('updated_at'); // TIMESTAMP
$table->timestamp('deleted_at')->nullable(); // TIMESTAMP NULL
其他类型
$table->boolean('is_active'); // BOOLEAN/TINYINT(1)
$table->json('metadata'); // JSON
$table->binary('data'); // BLOB
列修饰符
NULL / NOT NULL
$table->string('email')->nullable(); // 允许 NULL
$table->string('name'); // NOT NULL(默认)
默认值
$table->string('status')->default('pending');
$table->integer('views')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamp('created_at')->useCurrent(); // 使用当前时间
自增
$table->id(); // 自动创建自增主键
$table->bigIncrements('id'); // BIGINT AUTO_INCREMENT
$table->increments('id'); // INT AUTO_INCREMENT
唯一索引
$table->string('email')->unique(); // 添加唯一索引
$table->unique('email'); // 单独添加唯一索引
$table->unique(['email', 'phone']); // 复合唯一索引
索引
$table->index('email'); // 添加普通索引
$table->index(['user_id', 'status']); // 复合索引
$table->index('created_at', 'idx_created_at'); // 指定索引名
外键
$table->foreignId('user_id')
->constrained('users')
->onDelete('cascade');
// 或手动定义
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade')
->onUpdate('cascade');
注释
$table->string('status')->comment('用户状态:active, inactive');
位置
$table->string('phone')->after('email'); // 在 email 字段之后
$table->string('name')->first(); // 放在第一位
修改列
修改列类型
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->string('age')->change(); // 将 age 改为 VARCHAR
});
}
重命名列
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->renameColumn('old_name', 'new_name');
});
}
删除列
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->dropColumn('phone');
$table->dropColumn(['phone', 'address']); // 删除多个列
});
}
索引操作
添加索引
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->index('email');
$table->unique('email');
$table->index(['user_id', 'status']); // 复合索引
});
}
删除索引
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->dropIndex('users_email_index'); // 删除索引
$table->dropUnique('users_email_unique'); // 删除唯一索引
});
}
外键操作
添加外键
public function up(): void
{
$this->schema()->table('posts', function($table) {
$table->foreignId('user_id')
->constrained('users')
->onDelete('cascade');
});
}
删除外键
public function down(): void
{
$this->schema()->table('posts', function($table) {
$table->dropForeign(['user_id']);
});
}
数据迁移
迁移不仅可以修改表结构,还可以迁移数据:
public function up(): void
{
// 修改表结构
$this->schema()->table('users', function($table) {
$table->string('full_name')->nullable();
});
// 迁移数据
$users = db('users')->get();
foreach ($users as $user) {
db('users')
->where('id', $user['id'])
->update([
'full_name' => $user['first_name'] . ' ' . $user['last_name']
]);
}
// 删除旧列
$this->schema()->table('users', function($table) {
$table->dropColumn(['first_name', 'last_name']);
});
}
完整示例
创建用户表
<?php
declare(strict_types=1);
namespace Database\Migrations;
use Unicode\Framework\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up(): void
{
$this->schema()->create('users', function($table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->string('phone')->nullable();
$table->tinyInteger('status')->default(1)->comment('1:active, 0:inactive');
$table->timestamp('email_verified_at')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->index('email');
$table->index('status');
});
}
public function down(): void
{
$this->schema()->dropIfExists('users');
}
}
创建文章表(带外键)
<?php
declare(strict_types=1);
namespace Database\Migrations;
use Unicode\Framework\Database\Migrations\Migration;
class CreatePostsTable extends Migration
{
public function up(): void
{
$this->schema()->create('posts', function($table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->foreignId('category_id')->nullable()->constrained('categories')->onDelete('set null');
$table->string('title');
$table->text('content');
$table->string('slug')->unique();
$table->integer('views')->default(0);
$table->boolean('is_published')->default(false);
$table->timestamp('published_at')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->index(['user_id', 'is_published']);
$table->index('created_at');
});
}
public function down(): void
{
$this->schema()->dropIfExists('posts');
}
}
修改表结构
<?php
declare(strict_types=1);
namespace Database\Migrations;
use Unicode\Framework\Database\Migrations\Migration;
class AddAvatarToUsersTable extends Migration
{
public function up(): void
{
$this->schema()->table('users', function($table) {
$table->string('avatar')->nullable()->after('email');
$table->text('bio')->nullable()->after('avatar');
});
}
public function down(): void
{
$this->schema()->table('users', function($table) {
$table->dropColumn(['avatar', 'bio']);
});
}
}
最佳实践
1. 保持迁移文件小且专注
// ✅ 正确:一个迁移只做一件事
class AddEmailToUsersTable extends Migration { }
class AddPhoneToUsersTable extends Migration { }
// ❌ 错误:一个迁移做多件事
class UpdateUsersTable extends Migration {
// 添加 email、phone、address...
}
2. 总是实现 down 方法
// ✅ 正确:实现回滚逻辑
public function down(): void
{
$this->schema()->dropIfExists('users');
}
// ❌ 错误:没有回滚逻辑
public function down(): void
{
// 空方法
}
3. 使用描述性的迁移名称
// ✅ 正确:清楚说明目的
CreateUsersTable
AddEmailToUsersTable
RemovePhoneFromUsersTable
// ❌ 错误:名称不清晰
UpdateTable1
Migration20240101
4. 不要在生产环境直接修改迁移
// ❌ 错误:修改已运行的迁移
// 如果迁移已经运行,应该创建新的迁移来修改
// ✅ 正确:创建新的迁移
class ChangeEmailColumnType extends Migration { }
5. 测试迁移和回滚
<h1 id="运行迁移">运行迁移</h1>
php console migrate
<h1 id="回滚测试">回滚测试</h1>
php console migrate:rollback
<h1 id="再次运行">再次运行</h1>
php console migrate
6. 使用事务(如果数据库支持)
public function up(): void
{
$this->schema()->getConnection()->beginTransaction();
try {
// 迁移操作
$this->schema()->create('users', function($table) {
// ...
});
$this->schema()->getConnection()->commit();
} catch (\Exception $e) {
$this->schema()->getConnection()->rollBack();
throw $e;
}
}
常见问题
Q: 迁移文件应该放在哪里?
A: 放在 database/migrations/ 目录下。
Q: 如何重置数据库?
A: 使用 php console migrate:reset 回滚所有迁移,然后运行 php console migrate。
Q: 迁移失败怎么办?
A: 检查错误信息,修复迁移文件,然后重新运行。如果迁移已部分执行,可能需要手动清理。
Q: 可以在迁移中使用模型吗?
A: 可以,但不推荐。迁移应该专注于数据库结构,数据迁移应该使用查询构建器。
Q: 如何查看迁移状态?
A: 使用 php console migrate:status 查看哪些迁移已执行。