数据库迁移

⚠️ 注意:数据库迁移功能尚未实现

本文档描述的是计划中的迁移系统设计。当前版本(截至 v1.x)尚未实现此功能。以下内容仅供参考。

如需管理数据库结构,请直接使用 SQL 或第三方迁移工具(如 Phinx、Laravel Migration 等)。

数据库迁移提供了一种版本化管理数据库结构变更的方式,使团队协作中的数据库 schema 变更可追溯、可回滚。

基本概念

迁移文件位于项目的迁移目录中,每个迁移文件包含 up()(执行迁移)和 down()(回滚迁移)两个方法:

  • up():应用迁移,通常用于创建表、添加字段、创建索引等
  • down():撤销迁移,通常是 up() 的逆向操作

创建迁移文件

bash
# 创建新的迁移文件
php viswoole migrate:create create_users_table

生成的迁移文件结构如下:

php
<?php

use Viswoole\Database\Migration\Migration;
use Viswoole\Database\Schema\Blueprint;
use Viswoole\Database\Schema\Schema;

class CreateUsersTable extends Migration
{
    /**
     * 执行迁移
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 50);
            $table->string('email', 100)->unique();
            $table->string('password', 255);
            $table->tinyInteger('status')->default(1);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * 回滚迁移
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
}

常用字段类型

方法说明对应 MySQL 类型
$table->increments('id')自增主键INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
$table->bigIncrements('id')大整数自增主键BIGINT UNSIGNED AUTO_INCREMENT
$table->string('name', 50)可变长字符串VARCHAR(50)
$table->text('content')长文本TEXT
$table->integer('age')整数INT
$table->bigInteger('score')大整数BIGINT
$table->tinyInteger('status')小整数TINYINT
$table->decimal('price', 10, 2)精确小数DECIMAL(10,2)
$table->float('rate')浮点数FLOAT
$table->boolean('is_active')布尔值TINYINT(1)
$table->date('birthday')日期DATE
$table->dateTime('published_at')日期时间DATETIME
$table->timestamp('created_at')时间戳TIMESTAMP
$table->json('meta')JSON 类型JSON
$table->enum('level', ['A','B','C'])枚举ENUM('A','B','C')

常用修饰符

修饰符可链式调用来进一步定义字段属性:

php
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');

    // 默认值
    $table->string('name', 50)->default('匿名');

    // 允许为空
    $table->string('avatar')->nullable();

    // 唯一索引
    $table->string('email', 100)->unique();

    // 注释
    $table->text('remark')->comment('备注信息');

    // 在某个字段之后(MySQL 特有)
    $table->string('nickname')->after('name');

    // 无符号
    $table->integer('age')->unsigned();
});

常用辅助方法

方法说明
$table->timestamps()创建 created_atupdated_at 字段
$table->softDeletes()创建 deleted_at 软删除字段
$table->rememberToken()创建 remember_token 字段(用于"记住我"功能)
$table->dropTimestamps()移除时间戳字段
$table->dropSoftDeletes()移除软删除字段

修改表结构

添加字段

php
class AddPhoneToUsersTable extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone', 20)->nullable()->after('email');
            $table->string('address', 255)->nullable()->comment('收货地址');
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['phone', 'address']);
        });
    }
}

修改字段

php
class ModifyUsersEmailField extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            // 修改 email 字段长度
            $table->string('email', 200)->change();
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('email', 100)->change();
        });
    }
}

添加索引

php
class AddIndexToOrdersTable extends Migration
{
    public function up(): void
    {
        Schema::table('orders', function (Blueprint $table) {
            // 普通索引
            $table->index('user_id');

            // 组合索引
            $table->index(['user_id', 'status']);

            // 唯一索引
            $table->unique('order_no');

            // 指定索引名称
            $table->index('created_at', 'idx_orders_created_at');
        });
    }

    public function down(): void
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->dropIndex('idx_orders_created_at');
            $table->dropUnique('orders_order_no_unique');
        });
    }
}

执行迁移

bash
# 执行所有待处理的迁移
php viswoole migrate

# 执行迁移并填充数据
php viswoole migrate --seed

# 查看迁移状态
php viswoole migrate:status

# 回滚上一次迁移
php viswoole migrate:rollback

# 回滚所有迁移
php viswoole migrate:reset

# 回滚后重新执行所有迁移
php viswoole migrate:refresh

完整示例

以下是用户系统的完整迁移文件集合:

php
<?php

// === 001_create_users_table.php ===
use Viswoole\Database\Migration\Migration;
use Viswoole\Database\Schema\Blueprint;
use Viswoole\Database\Schema\Schema;

class CreateUsersTable extends Migration
{
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 50)->comment('用户昵称');
            $table->string('email', 100)->unique()->comment('邮箱');
            $table->string('password', 255)->comment('密码哈希');
            $table->string('phone', 20)->nullable()->comment('手机号');
            $table->tinyInteger('status')->default(1)->comment('状态:1正常 0禁用');
            $table->timestamps();
            $table->softDeletes();

            $table->index('status', 'idx_users_status');
            $table->index('created_at', 'idx_users_created_at');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('users');
    }
}

// === 002_create_profiles_table.php ===
class CreateProfilesTable extends Migration
{
    public function up(): void
    {
        Schema::create('profiles', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id')->comment('用户ID');
            $table->string('avatar', 255)->nullable()->comment('头像URL');
            $table->text('bio')->nullable()->comment('个人简介');
            $table->string('city', 50)->nullable()->comment('所在城市');
            $table->timestamps();

            $table->foreign('user_id')
                  ->references('id')
                  ->on('users')
                  ->onDelete('cascade');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('profiles');
    }
}

// === 003_create_orders_table.php ===
class CreateOrdersTable extends Migration
{
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('order_no', 32)->unique()->comment('订单编号');
            $table->unsignedInteger('user_id')->comment('用户ID');
            $table->decimal('total_amount', 12, 2)->default(0)->comment('订单总额');
            $table->tinyInteger('status')->default(0)->comment('订单状态');
            $table->timestamp('paid_at')->nullable()->comment('支付时间');
            $table->timestamps();
            $table->softDeletes();

            $table->index('user_id', 'orders_user_id_index');
            $table->index(['user_id', 'status'], 'orders_user_status_index');
            $table->index('created_at', 'orders_created_at_index');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
}

最佳实践

  1. 命名规范:迁移文件采用描述性命名,如 create_users_tableadd_phone_to_users_table
  2. 可回滚:始终实现 down() 方法,确保迁移可完全回滚
  3. 小步迭代:每次迁移只做一件事,避免大而全的迁移文件
  4. 团队协作:迁移文件纳入版本控制,团队成员拉取代码后执行 migrate 即可同步数据库结构
  5. 生产环境谨慎操作:生产环境执行迁移前务必备份数据库