Skip to content
第 179 / 250 章Node⏱ 10 分钟阅读

第 179 章:Migration 数据迁移

学习目标

  • 理解 Migration 的作用
  • 学会生成和执行迁移
  • 掌握生产环境最佳实践
  • 学会回滚与版本管理

一、什么是 Migration

Migration 是数据库的版本控制系统,记录表结构的每次变更。

二、配置

2.1 关闭 synchronize

typescript
// app.module.ts
TypeOrmModule.forRoot({
  // ...
  synchronize: false,   // ⚠️ 生产环境必须 false
  migrations: [__dirname + '/migrations/*{.ts,.js}'],
  migrationsRun: false,  // 启动时自动运行,通常 false,手动跑
}),

2.2 DataSource(迁移脚本专用)

typescript
// data-source.ts
import { DataSource } from 'typeorm';
import { User } from './user.entity';
import { Post } from './post.entity';

export default new DataSource({
  type: 'mysql',
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  username: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  entities: [User, Post],
  migrations: ['src/migrations/*.ts'],
});

三、生成迁移

3.1 自动生成

bash
# 根据 entity 与数据库的差异生成迁移
pnpm typeorm migration:generate -d src/data-source.ts src/migrations/Init

3.2 手动创建

bash
pnpm typeorm migration:create src/migrations/AddUserEmail

3.3 添加 npm scripts

json
{
  "scripts": {
    "migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/data-source.ts",
    "migration:create": "typeorm-ts-node-commonjs migration:create",
    "migration:run": "typeorm-ts-node-commonjs migration:run -d src/data-source.ts",
    "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/data-source.ts",
    "migration:show": "typeorm-ts-node-commonjs migration:show -d src/data-source.ts"
  }
}

四、迁移文件结构

typescript
// src/migrations/1700000000000-Init.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class Init1700000000000 implements MigrationInterface {
  // 升级(向上)
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment' },
          { name: 'name', type: 'varchar', length: '50' },
          { name: 'email', type: 'varchar', isUnique: true },
          { name: 'age', type: 'int', default: 18 },
          { name: 'createdAt', type: 'timestamp', default: 'CURRENT_TIMESTAMP' },
          { name: 'updatedAt', type: 'timestamp', default: 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' },
        ],
      }),
      true,
    );
  }

  // 回滚(向下)
  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

五、常用迁移操作

5.1 表操作

typescript
public async up(queryRunner: QueryRunner) {
  // 创建表
  await queryRunner.createTable(new Table({ ... }));

  // 删除表
  await queryRunner.dropTable('users');

  // 重命名表
  await queryRunner.renameTable('user', 'users');
}

5.2 列操作

typescript
public async up(queryRunner: QueryRunner) {
  // 加列
  await queryRunner.addColumn('users', new TableColumn({
    name: 'avatar',
    type: 'varchar',
    isNullable: true,
  }));

  // 删列
  await queryRunner.dropColumn('users', 'avatar');

  // 改列
  await queryRunner.changeColumn('users', 'age', new TableColumn({
    name: 'age',
    type: 'int',
    default: 0,
  }));

  // 重命名列
  await queryRunner.renameColumn('users', 'name', 'username');
}

5.3 索引

typescript
public async up(queryRunner: QueryRunner) {
  // 加索引
  await queryRunner.createIndex('users', new TableIndex({
    name: 'IDX_EMAIL',
    columnNames: ['email'],
  }));

  // 唯一索引
  await queryRunner.createIndex('users', new TableIndex({
    name: 'UQ_EMAIL',
    columnNames: ['email'],
    isUnique: true,
  }));

  // 删索引
  await queryRunner.dropIndex('users', 'IDX_EMAIL');
}

5.4 外键

typescript
public async up(queryRunner: QueryRunner) {
  // 加外键
  await queryRunner.createForeignKey('posts', new TableForeignKey({
    columnNames: ['authorId'],
    referencedColumnNames: ['id'],
    referencedTableName: 'users',
    onDelete: 'CASCADE',
  }));
}

六、运行迁移

6.1 执行所有待执行

bash
pnpm migration:run

输出示例:

query: SELECT * FROM "migrations"
query: START TRANSACTION
query: INSERT INTO "migrations"("timestamp", "name") VALUES (?, ?) -- PARAMETERS: [1700000000000,"Init1700000000000"]
query: COMMIT
Migration Init1700000000000 has been executed successfully.

6.2 回滚最近一次

bash
pnpm migration:revert

6.3 查看状态

bash
pnpm migration:show
[X] Init1700000000000
[X] AddUserEmail1700000001000
[ ] AddPostTable1700000002000

七、数据迁移

typescript
public async up(queryRunner: QueryRunner) {
  // 先加列
  await queryRunner.addColumn('users', new TableColumn({
    name: 'status',
    type: 'varchar',
    default: "'active'",
  }));

  // 填充数据
  await queryRunner.query(
    `UPDATE users SET status = 'active' WHERE status IS NULL`,
  );
}

八、生产环境流程

最佳实践:

bash
# 1. 本地开发
pnpm migration:generate src/migrations/AddXxx

# 2. 检查生成的 SQL
cat src/migrations/1700000000000-AddXxx.ts

# 3. 提交代码
git add .
git commit -m "feat: add migration for xxx"

# 4. 部署时执行
pnpm migration:run

九、迁移注意事项

9.1 不要修改已部署的迁移

typescript
// ❌ 错误:已部署后修改
export class Init1700000000000 {
  // 改了这里,生产会报错
}

// ✅ 正确:新建一个迁移
export class UpdateUsersInit1700000099999 {}

9.2 复杂变更分步执行

typescript
// 第一步:加新字段(无破坏)
// 2024-01-01-add-new-column.ts

// 第二步:数据迁移
// 2024-01-02-migrate-data.ts

// 第三步:删除旧字段
// 2024-01-03-remove-old-column.ts

9.3 备份先于迁移

bash
mysqldump -u root -p myapp > backup_$(date +%Y%m%d).sql
pnpm migration:run

十、CI/CD 集成

yaml
# .github/workflows/deploy.yml
- name: Run migrations
  run: pnpm migration:run
  env:
    DB_HOST: ${{ secrets.DB_HOST }}
    DB_USER: ${{ secrets.DB_USER }}
    DB_PASS: ${{ secrets.DB_PASS }}
    DB_NAME: ${{ secrets.DB_NAME }}

十一、本章小结

命令作用
migration:generate自动生成
migration:create手动创建
migration:run执行迁移
migration:revert回滚一次
migration:show查看状态
方法作用
createTable建表
addColumn加列
dropColumn删列
createIndex建索引
createForeignKey建外键
query原始 SQL

动手练习

  1. 创建 User 实体并生成第一个迁移
  2. 执行迁移,然后回滚
  3. 加一个 email 字段的迁移(用 addColumn)
  4. 用 migration:show 查看状态

推荐阅读


下一章:第 180 章:数据库高级特性

本站基于 VitePress 构建 · 由 Codebook 团队维护