迁移教程
数据库迁移(Migrations)是 TypeORM 中管理数据库表结构变更的核心方案,替代了 synchronize: true(仅适合开发环境),能够精准控制表结构的创建、修改、删除,保证开发、测试、生产环境的表结构一致,同时支持回滚操作,避免手动改表导致的环境不一致问题。
一、迁移的核心作用
- 环境一致性:所有表结构变更通过迁移文件管理,团队成员/不同环境执行相同迁移,保证表结构一致;
- 可追溯性:每一个迁移文件对应一次表结构变更,可清晰追溯变更历史;
- 可回滚性:支持回滚到任意历史版本,解决变更出错的问题;
- 生产安全:替代
synchronize: true(生产禁用),避免自动同步导致的表结构意外变更。
二、迁移基础配置
首先需在 data-source.ts 中配置迁移文件路径:
// data-source.ts
import { DataSource } from "typeorm";
export const AppDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "your-password",
database: "typeorm_demo",
entities: ["src/entity/**/*.ts"],
migrations: ["src/migration/**/*.ts"], // 迁移文件路径
migrationsTableName: "typeorm_migrations", // 迁移历史表名(默认)
synchronize: false, // 生产环境必须禁用
});同时在 package.json 中添加迁移相关脚本(简化命令):
{
"scripts": {
"migration:generate": "typeorm-ts-node-commonjs migration:generate src/migration/$npm_config_name -d src/data-source.ts",
"migration:create": "typeorm-ts-node-commonjs migration:create src/migration/$npm_config_name",
"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"
}
}说明:
typeorm-ts-node-commonjs适配 TypeScript + CommonJS 模块,若使用 ESModules 需替换为typeorm-ts-node-esm。
三、迁移核心命令
1. 创建空迁移文件
手动创建一个空的迁移文件,用于编写自定义表结构变更:
# 通过脚本创建(指定名称,如 CreateUserTable)
npm run migration:create --name=CreateUserTable
# 原生命令(不推荐,需手动指定路径)
typeorm-ts-node-commonjs migration:create src/migration/CreateUserTable执行后会在 src/migration 目录生成文件:{时间戳}-CreateUserTable.ts,包含 up(执行迁移)和 down(回滚迁移)两个方法。
2. 自动生成迁移文件(推荐)
根据实体与数据库表的差异,自动生成迁移文件(无需手动编写基础变更):
# 通过脚本生成(指定名称,如 AddAgeColumnToUser)
npm run migration:generate --name=AddAgeColumnToUser
# 原生命令
typeorm-ts-node-commonjs migration:generate src/migration/AddAgeColumnToUser -d src/data-source.ts关键:生成前需确保实体已修改完成,且数据库连接正常,TypeORM 会对比实体和当前数据库表结构,自动生成变更逻辑。
3. 运行迁移(执行表结构变更)
执行所有未执行的迁移文件,更新数据库表结构:
# 通过脚本运行
npm run migration:run
# 原生命令
typeorm-ts-node-commonjs migration:run -d src/data-source.ts执行后,迁移历史表(typeorm_migrations)会记录已执行的迁移文件,避免重复执行。
4. 回滚迁移(撤销最后一次变更)
回滚最后一次执行的迁移(执行迁移文件的 down 方法):
# 通过脚本回滚
npm run migration:revert
# 原生命令
typeorm-ts-node-commonjs migration:revert -d src/data-source.ts注意:回滚仅支持按执行顺序撤销,若需回滚到指定版本,需多次执行
revert。
5. 查看迁移状态
查看所有迁移文件的执行状态(已执行/未执行):
# 通过脚本查看
npm run migration:show
# 原生命令
typeorm-ts-node-commonjs migration:show -d src/data-source.ts四、迁移文件编写(核心)
迁移文件的核心是 up(执行变更)和 down(回滚变更)方法,TypeORM 提供 QueryRunner 工具操作数据库表结构,支持创建表、修改字段、添加索引、数据迁移等所有操作。
1. 创建表(Create Table)
// src/migration/{时间戳}-CreateUserTable.ts
import { MigrationInterface, QueryRunner, Table } from "typeorm";
export class CreateUserTable1710000000000 implements MigrationInterface {
// 执行迁移:创建 user 表
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "user", // 表名
columns: [
{
name: "id",
type: "int",
isPrimary: true,
isGenerated: true,
generationStrategy: "increment", // 自增
isUnsigned: true, // 无符号
},
{
name: "username",
type: "varchar",
length: "50",
isUnique: true, // 唯一索引
isNullable: false, // 非空
},
{
name: "nickname",
type: "varchar",
length: "50",
default: "'未知用户'", // 默认值(注意引号)
},
{
name: "age",
type: "tinyint",
isNullable: true,
},
{
name: "created_at",
type: "datetime",
default: "CURRENT_TIMESTAMP", // 默认当前时间
},
],
}),
true // 是否检查表是否已存在,避免重复创建
);
}
// 回滚迁移:删除 user 表
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("user");
}
}2. 修改字段(Alter Column)
// src/migration/{时间戳}-ModifyUserAgeColumn.ts
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class ModifyUserAgeColumn1710000000001 implements MigrationInterface {
// 执行迁移:修改 age 字段(非空 + 默认 0)
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.changeColumn(
"user", // 表名
"age", // 原字段名
new TableColumn({
name: "age",
type: "tinyint",
isNullable: false, // 改为非空
default: "0", // 添加默认值
})
);
}
// 回滚迁移:恢复 age 字段为可空
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.changeColumn(
"user",
"age",
new TableColumn({
name: "age",
type: "tinyint",
isNullable: true,
})
);
}
}3. 添加/删除索引
// src/migration/{时间戳}-AddIndexToUserNickname.ts
import { MigrationInterface, QueryRunner, TableIndex } from "typeorm";
export class AddIndexToUserNickname1710000000002 implements MigrationInterface {
// 执行迁移:添加 nickname 普通索引
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createIndex(
"user",
new TableIndex({
name: "IDX_USER_NICKNAME", // 索引名
columnNames: ["nickname"], // 索引字段
isUnique: false, // 非唯一索引
})
);
}
// 回滚迁移:删除索引
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropIndex("user", "IDX_USER_NICKNAME");
}
}4. 添加外键
// src/migration/{时间戳}-AddForeignKeyToOrder.ts
import { MigrationInterface, QueryRunner, TableForeignKey } from "typeorm";
export class AddForeignKeyToOrder1710000000003 implements MigrationInterface {
// 执行迁移:给 order 表添加 user_id 外键
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createForeignKey(
"order", // 从表名
new TableForeignKey({
name: "FK_ORDER_USER", // 外键名
columnNames: ["user_id"], // 从表字段
referencedTableName: "user", // 主表名
referencedColumnNames: ["id"], // 主表字段
onDelete: "CASCADE", // 主表删除时,从表数据级联删除
onUpdate: "CASCADE", // 主表更新时,从表外键级联更新
})
);
}
// 回滚迁移:删除外键
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropForeignKey("order", "FK_ORDER_USER");
}
}5. 数据迁移(插入/更新/删除数据)
// src/migration/{时间戳}-InsertAdminUser.ts
import { MigrationInterface, QueryRunner } from "typeorm";
export class InsertAdminUser1710000000004 implements MigrationInterface {
// 执行迁移:插入管理员用户
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
INSERT INTO user (username, nickname, age)
VALUES ('admin', '管理员', 20)
`);
}
// 回滚迁移:删除管理员用户
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DELETE FROM user WHERE username = 'admin'
`);
}
}五、不同数据库的迁移差异
TypeORM 迁移适配主流数据库,但部分语法/特性存在差异,需注意:
1. MySQL/MariaDB
- 支持
unsigned、zerofill等专属字段属性; - 外键
onDelete/onUpdate支持CASCADE``SET NULL``RESTRICT等; - 时间字段推荐用
datetime,timestamp有范围限制。
2. PostgreSQL
- 字段类型差异:
varchar→character varying,int→integer; - 支持
jsonb类型(MySQL 仅支持json); - 索引/外键命名规则更严格,避免特殊字符。
3. SQLite
- 对 ALTER TABLE 支持有限(无法直接修改字段类型/删除字段);
- 无外键约束(需手动开启
PRAGMA foreign_keys = ON); - 迁移回滚时需谨慎,部分操作无法撤销。
4. MongoDB
- 迁移仅支持数据操作(插入/更新/删除),无表结构变更(NoSQL 无表概念);
- 需使用
queryRunner.query执行原生 MongoDB 命令。
六、生产环境迁移最佳实践
- 禁用自动同步:确保
synchronize: false,避免自动修改表结构; - 先测试后执行:迁移在测试环境验证通过后,再部署到生产环境;
- 备份数据:执行迁移前备份数据库,防止变更出错导致数据丢失;
- 分批次执行:大规模表结构变更(如大表加索引)分批次执行,避免锁表;
- 保留迁移文件:所有迁移文件需纳入版本控制(Git),不可删除;
- 避免手动改表:生产环境表结构变更必须通过迁移文件,禁止手动执行 SQL;
- 监控迁移执行:执行迁移后检查迁移历史表和表结构,确认变更生效;
- 回滚预案:针对重要变更,提前验证
down方法的有效性,确保可回滚。
七、常见问题解决
迁移生成空文件:
- 原因:实体与数据库表结构无差异;
- 解决:检查实体修改是否正确,或数据库连接是否指向正确环境。
迁移执行失败(锁表):
- 原因:大表执行 ALTER TABLE 导致锁表;
- 解决:非高峰期执行,或使用在线 DDL 工具(如 MySQL 的 pt-online-schema-change)。
回滚失败:
- 原因:
down方法编写不完整,或数据已被修改; - 解决:完善
down方法,或手动恢复数据/表结构。
- 原因:
至此,本章节的学习就到此结束了,如有疑惑,可对接技术客服进行相关咨询。