数据库迁移与种子:版本控制你的数据库

FreeGuideOnline 最新 2026-06-16

什么是数据库迁移与种子

数据库迁移(Migration)和种子(Seed)是现代应用开发中管理数据库结构变更和初始数据的核心机制。它们将数据库的演进纳入版本控制,使团队协作变得安全、可重复且可追踪。

  • 迁移(Migration):通过代码定义数据库结构的变更(如创建表、添加列、修改索引),而不是直接操作数据库服务器。每一次变更都记录为一个带时间戳的脚本文件,可以向前应用(up)或向后回滚(down)。
  • 种子(Seed):用于向数据库填充应用程序运行所需的初始数据或测试数据,例如默认管理员账号、国家列表、分类目录等。种子通常在迁移完成之后执行。

把迁移与种子引入你的项目,意味着数据库的“状态”变成了代码,可以被提交到 Git,在任何环境下精确复现。


为什么需要迁移与种子

传统方式带来的问题

在没有迁移工具时,开发者经常需要:

  • 手动导出 SQL 转储,再导入另一环境,极易出现表结构不同步。
  • 在团队中通过邮件或聊天软件传递 SQL 脚本,版本混乱,难以知道哪条语句已执行。
  • 生产环境与开发环境出现“它在我机器上能跑”的结构性差异。
  • 无法快速重置或销毁数据库结构,测试成本高。

迁移与种子带来的优势

  • 版本控制一致性:数据库的结构变更与应用程序代码放在同一个仓库中,检出任意版本即可获得对应的数据库结构。
  • 可重复部署:在新环境重建数据库时,只需运行迁移命令,再执行种子即可得到完全相同的基线。
  • 团队协作安全:每个开发者提交迁移文件,经过代码审查后合并,确保改动清晰可控。
  • 回滚能力:多数迁移框架支持 down 方法,可以撤销最近的变更,快速应对上线问题。
  • 测试隔离:可以在测试框架中自动构建并销毁数据库,保证每次测试都在干净的数据集上运行。

迁移文件的基本结构

以最常见的 Laravel(PHP)风格为例,一个迁移类包含 updown 两个方法:

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

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->timestamps();
        });
    }

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

其他框架(如 Django、TypeORM、Prisma、Entity Framework)的迁移原理非常相似,都是将数据库操作抽象成编程语言方法,而非手写原生 SQL。

迁移文件的命名约定

迁移文件通常包含时间戳,以确保执行顺序唯一:

2025_01_01_120000_create_posts_table.php
2025_01_02_093000_add_is_published_to_posts.php

当运行迁移命令时,框架会按时间戳顺序执行尚未运行过的文件,并在数据库中记录已执行的迁移日志(通常存放在 migrations 表中)。


迁移的常用操作

下面展示日常开发中最频繁使用的迁移操作,并强调最佳实践。

创建表与字段类型

Schema::create('products', function (Blueprint $table) {
    $table->id();                     // 自增主键
    $table->string('name', 150);      // 带长度限制的字符串
    $table->decimal('price', 8, 2);   // 精确小数
    $table->text('description');      // 长文本
    $table->boolean('is_active')->default(true);
    $table->timestamps();             // created_at 和 updated_at
});

修改现有表

使用独立的迁移文件修改表,绝不要回退到旧迁移中去修改,这会破坏其他环境的迁移链。

Schema::table('products', function (Blueprint $table) {
    $table->string('sku')->nullable()->after('name');
    $table->integer('stock')->default(0);
});

添加索引与外键

// 普通索引
$table->index('sku');

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

// 外键约束(注意指定引用表和 onDelete 行为)
$table->foreignId('user_id')
      ->constrained()
      ->onDelete('cascade');

重命名列或删除列

// 重命名:需要安装 doctrine/dbal 包
$table->renameColumn('title', 'post_title');

// 删除列
$table->dropColumn('old_field');

遵循“每次只做一件事”的原则,让每个迁移文件只负责一个明确的结构变更,便于审查和回滚。


运行与回滚迁移

不同框架的命令行工具略有不同,但核心逻辑一致。

Laravel

# 运行所有未执行的迁移
php artisan migrate

# 回滚最后一个迁移批次(对应当次执行的一组文件)
php artisan migrate:rollback

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

# 重建整个数据库(drop 所有表,再执行迁移)
php artisan migrate:fresh

Django

# 创建迁移文件
python manage.py makemigrations

# 执行迁移
python manage.py migrate

# 回滚到某个迁移点
python manage.py migrate app_name 0002_previous_migration

TypeORM / Knex.js / Prisma 等

这些工具同样提供类似的 CLI 命令(如 typeorm migration:runknex migrate:latestprisma migrate dev),核心流程均为:创建迁移 → 应用迁移 → 标记已执行

生产环境的迁移策略

在生产环境执行迁移时:

  1. 始终先在测试/预发布环境验证迁移。
  2. 备份数据库。
  3. 对于可能长时间锁表的操作(如大表增加索引),选择低峰期执行,并考虑使用在线 DDL 工具(如 pt-online-schema-change、gh-ost)。
  4. 尽量保持向前兼容。例如新增列使用 nullable 或提供默认值,避免旧代码因缺少字段而报错。

种子数据(Database Seeding)

种子器用于填充应用程序启动或开发阶段所需的基础数据。它保证了每个环境都有一套一致的基础数据集。

创建种子类

在 Laravel 中,种子类位于 database/seeders/

use Illuminate\Database\Seeder;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    public function run()
    {
        User::create([
            'name' => 'Admin',
            'email' => 'admin@app.com',
            'password' => Hash::make('password'),
            'role' => 'admin',
        ]);

        // 使用工厂批量生成测试用户
        User::factory()->count(50)->create();
    }
}

调用多个种子

可以在 DatabaseSeeder 中集中调度:

public function run()
{
    $this->call([
        UserSeeder::class,
        CategorySeeder::class,
        TagSeeder::class,
    ]);
}

运行种子命令:

# Laravel 执行所有未执行的种子
php artisan db:seed

# 重新执行迁移并种子
php artisan migrate:fresh --seed

种子在生产中的使用

生产环境种子通常只包含真正必需的配置数据,如计费套餐、权限角色、国家代码等。这些数据不应通过 UI 手动录入,而是通过种子保证准确性并实现自动化部署。

重要区分

  • 基础/配置数据:通过种子交付,必须纳入版本控制。
  • 用户生成内容:绝对不能通过种子覆盖生产数据。
  • 测试假数据:在开发/测试环境中使用模型工厂生成,不应出现在生产种子中。

结合版本控制的最佳实践

将迁移文件纳入 Git

  • database/migrations/ (或框架对应的迁移目录)必须提交到版本库。
  • 永远不要直接修改生产数据库的结构,一切变更都要通过创建新的迁移文件来完成。

养成“迁移+种子”的开发流程

  1. 创建新分支。
  2. 生成迁移文件,定义结构变更。
  3. 编写种子,补充这次变更所需的初始数据(如果需要)。
  4. 本地执行 migrate fresh --seed 验证。
  5. 提交代码,发起合并请求。
  6. 在部署流水线中自动执行 migrate 命令。

不要生成“修复”迁移

如果团队中的某个迁移有问题,此时应创建一个新的迁移文件来修正错误,而不是修改已提交到主分支的旧迁移。否则其他开发者的数据库会与你产生差异,引发“迁移已应用但文件已变更”的致命问题。

拆分大迁移

如果一张表需要频繁改动,不要反复添加列,而是计划好一次较大的重构迁移,在测试环境充分验证后再合入。


常见框架迁移工具对照

框架 / ORM 迁移工具 种子方式
Laravel (PHP) Artisan migrate db:seed + Seeder 类
Django (Python) makemigrations + migrate loaddata 或自定义管理命令
Rails (Ruby) rails db:migrate db/seeds.rb
TypeORM (TS/JS) typeorm migration:run 通常结合工厂自行实现
Knex.js (JS) knex migrate:latest knex seed:run
Prisma (TS/JS) prisma migrate dev prisma db seed
Entity Framework Core dotnet ef migrations add HasData 方法或自定义

虽然命令不同,但思想完全一致:用代码管理数据库的演变历史,用种子注入必要的基础数据。


总结

数据库迁移与种子让你将数据库的“版本”与应用程序代码视为一个整体。通过迁移定义结构变化,通过种子填充初始数据,你的团队将彻底告别手工执行 SQL 的低效与风险。立即开始在你的项目中遵循这一工作流,你将在协作速度、部署安全性和环境重建的便捷性上体会到巨大提升。