第 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/Init3.2 手动创建
bash
pnpm typeorm migration:create src/migrations/AddUserEmail3.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:revert6.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.ts9.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 |
动手练习
- 创建 User 实体并生成第一个迁移
- 执行迁移,然后回滚
- 加一个 email 字段的迁移(用 addColumn)
- 用 migration:show 查看状态
推荐阅读
下一章:第 180 章:数据库高级特性 →