数据库迁移

最后更新: 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 { // 回滚逻辑 } }

    命名规范

  • 类名使用大驼峰命名:CreateUsersTable
  • 文件名使用下划线命名:create_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 查看哪些迁移已执行。

    相关文档

  • 查询构建器 - 数据库查询
  • ORM 使用 - 模型操作
  • Schema 构建器 - 表结构操作