首页
/ TypeORM 手工编写迁移(Migration)全指南:migration:create 与 up/down 实战

TypeORM 手工编写迁移(Migration)全指南:migration:create 与 up/down 实战

2026-09-08 11:39:07作者:冯梦姬Eddie

在 TypeORM 中,迁移(Migration)是"一个包含 SQL 查询、用于更新数据库 schema 的单文件"。当项目上线数月、数据库里已沉淀真实数据后,直接依赖 synchronize: true 自动同步结构是危险的,迁移则提供了版本化、可回滚、按时间戳顺序执行的安全变更途径。本文围绕官方文档 docs/docs/migrations/03-creating.md 展开,讲解如何用 migration:create 手工创建迁移文件、理解 up / down 方法职责、借助 QueryRunner 编写与回滚 SQL,并结合仓库源码(MigrationCreateCommand.tsMigrationInterface.tsMigrationExecutor.ts)还原 CLI 到底做了什么、执行时如何被调度。读完你将能独立手工产出规范、可运行的 TypeORM 迁移并理解其底层运行机制。

为什么需要手工创建迁移

在生产环境中,"改实体代码让 TypeORM 自动同步数据库"并非好主意:一旦数据库已存在数据,synchronize: true 可能触发破坏性的结构变更,且没有任何版本与回滚痕迹。迁移把数据库演进变成一串有名字、有时间戳、可追溯的文件——每次发布对应一个迁移,先在开发库验证,再按序执行到生产库。官方背景见 docs/docs/migrations/01-why.md

典型场景正如原文档所述:假设线上已有运行数月的 Post 实体,字段是 title,现在新版本需要把列改名为 name。数据库里有成千上万条 post 记录,不能简单删除重建。这时最稳妥的做法就是新增一条迁移,执行(PostgreSQL 方言):

ALTER TABLE "post" RENAME COLUMN "title" TO "name";

TypeORM 提供的"迁移"正是让你安全书写并运行这类 SQL 的地方,而 migration:create 则是手工开启这一过程的入口。

使用 migration:create 生成迁移骨架

基本命令与产物

TypeORM 提供 migration:create 命令,通过指定迁移名称与存放目录来生成一个空迁移文件:

npx typeorm migration:create <path/to/migrations>/<migration-name>

例如:

npx typeorm migration:create src/db/migrations/post-refactoring

执行完成后,src/db/migrations 目录下会新增一个命名为 {TIMESTAMP}-post-refactoring.ts 的文件,其中 {TIMESTAMP} 是生成文件时当前时间戳(毫秒级),用于保证迁移文件的全局唯一顺序。从 MigrationCreateCommand.tshandler 实现可以看到具体的组装逻辑:

const timestamp = CommandUtils.getTimestamp(args.timestamp)
const fullPath =
    path.dirname(inputPath) + "/" + timestamp + "-" + filename

即文件名 = 目录 + "/" + 时间戳 + "-" + 你传入的名称,随后通过 CommandUtils.createFile 递归创建目录并写出文件(见 CommandUtils.ts)。

需要特别说明的是:命令中的 <migration-name> 仅是"人类可读后缀",真正的顺序权威是前面的时间戳。所以请勿手改时间戳前缀来"人工排序";若确有需要控制时间戳,可使用下面的 --timestamp 参数。

生成的骨架内容

打开生成的文件,你会看到如下内容:

import { MigrationInterface, QueryRunner } from "typeorm"

export class PostRefactoringTIMESTAMP implements MigrationInterface {
    async up(queryRunner: QueryRunner): Promise<void> {}

    async down(queryRunner: QueryRunner): Promise<void> {}
}

对照源码中 getTemplate,骨架由三部分构成:

  • 导入 MigrationInterfaceQueryRunner
  • 类名 = 对传入名称做 camelCase 处理(首字母大写)后再拼接时间戳。源码为 export class ${camelCase(name, true)}${timestamp},其中 camelCase 定义于 StringUtils.ts。因此 post-refactoring 会变成 PostRefactoring,最终类名为 PostRefactoring1700000000000
  • 类实现了 MigrationInterface,内含两个待填充的异步方法 updown,它们都接收一个 QueryRunner 参数。

提示:迁移类名中的时间戳来自 Migration.ts 所描述的 timestamp 字段语义——它指示了迁移的执行顺序,TypeORM 运行时正是依据它排序、并判断哪些迁移已经执行过。因此请保持类名与文件名的一致性,不要随意改动类名后缀。

up 与 down:正向执行与逆向回滚

生成的骨架中有两个必须填充代码的方法:

  • up:包含执行迁移所需的全部代码(建表、加列、改名、改约束、DML 数据订正等);
  • down:回滚 up 所做的一切变更,用于撤销上一次迁移。

从接口定义看(MigrationInterface.ts),updown 都接收 QueryRunner 并返回 Promise:

export interface MigrationInterface {
    up(queryRunner: QueryRunner): Promise<any>
    down(queryRunner: QueryRunner): Promise<any>
}

这两个方法的契约可以概括为"互为逆操作":up 如何把 schema 从 A 变为 B,down 就必须把 schema 从 B 准确地还原为 A。写 down 时务必针对 up 的每一条语句给出对称的反向语句。

通过 QueryRunner 执行数据库操作

up / down 内部,所有数据库操作都通过 QueryRunner 对象完成。它是 TypeORM 中"单一数据库连接上执行查询"的抽象:既支持 queryRunner.query(...) 运行原生 SQL,也支持 queryRunner.createTable / addColumn / renameColumn / dropColumn 等高级 schema 操作 API。关于 QueryRunner 的完整能力(如事务控制、流式查询、表/列/索引/外键管理方法),见 docs/docs/query-runner.md

迁移执行器对 up 的调度逻辑位于 MigrationExecutor.ts:在确保 migrations 记录表存在后,依次调用 queryRunner.beforeMigration()migration.instance.up(queryRunner)queryRunner.afterMigration()insertExecutedMigration(...)。这也就是说,你的 up 方法返回的 Promise 被 resolve 后,该迁移才会被记为"已执行";任何抛错都会中断流程并触发事务回滚(取决于事务模式),避免出现"跑了一半、记录却说没跑"的脏状态。

完整示例:重命名 Post 表列

沿用原文档 Post 实体字段 titlename 的改造,迁移内容如下:

import { MigrationInterface, QueryRunner } from "typeorm"

export class PostRefactoringTIMESTAMP implements MigrationInterface {
    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `ALTER TABLE "post" RENAME COLUMN "title" TO "name"`,
        )
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `ALTER TABLE "post" RENAME COLUMN "name" TO "title"`,
        ) // reverts things made in "up" method
    }
}

注意点:

  1. SQL 是目标数据库方言——示例为 PostgreSQL,因此表名/列名带双引号。若你使用 MySQL / SQLite / SQL Server / Oracle 等,需要改成对应方言(例如 MySQL 使用反引号 `post`)。从驱动实现看,TypeORM 只是把字符串透传给底层驱动,方言正确性由你负责;
  2. 如果 up 里做了多条变更,down 里要以相反顺序逐条还原,确保中途失败也不会残留不一致;
  3. 强烈建议为每个迁移编写一条"目标可逆"的完整上下对应,保证未来 migration:revert 可用(见 docs/docs/migrations/06-reverting.md)。

migration:create 的扩展选项

虽然原文档只演示了最简形式,MigrationCreateCommand.ts 中还暴露了三个实用选项,理解它们能更好地应对不同工程形态:

选项 别名 类型 默认值 作用
-o --outputJs boolean false 生成 JavaScript(.js)而非 TypeScript(.ts)迁移文件
--esm boolean false -o 搭配,生成 ESM 风格(export class)而非 CommonJS(module.exports =
-t --timestamp number 当前时间 为迁移指定自定义时间戳,需为非负数,否则抛出 TypeORMError: timestamp option should be a non-negative number

示例:

# 生成 JS(CommonJS)迁移
npx typeorm migration:create -o src/db/migrations/post-refactoring

# 生成 JS(ESM)迁移
npx typeorm migration:create -o --esm src/db/migrations/post-refactoring

# 用自定义时间戳生成
npx typeorm migration:create -t 1700000000000 src/db/migrations/post-refactoring

getJavascriptTemplate 可以看出,JS 迁移同样实现 MigrationInterface 语义:up(queryRunner)down(queryRunner) 方法签名不变,仅省略了 TypeScript 类型标注,并用 export class(ESM)或 module.exports =(CommonJS)导出。

为什么需要 -o?TS 迁移的运行前提

一个常见误区:migration:create / migration:generate 默认产出的是 .ts 文件,而 migration:runmigration:revert 只直接消费 .js 文件。因此直接跑 migration:run 前通常需要:

  1. tsc 把 TS 迁移编译为 JS;或
  2. 使用 ts-node 相关入口运行 .ts 迁移,例如(详见 docs/docs/migrations/05-executing.md):
# CommonJS 项目
npx typeorm-ts-node-commonjs migration:run -- -d path-to-datasource-config

# ESM 项目
npx typeorm-ts-node-esm migration:run -- -d path-to-datasource-config

这就是 -o 的典型用武之地:某些不经过 TS 编译的部署环境(如纯 JS 工程)可直接生成 .js 迁移省去编译环节。

从源码看迁移从"手工文件"到"被记录"的完整链路

将手工创建的迁移投入实际使用前,还需在 DataSource 配置中注册它们——官方 Setup 指南见 docs/docs/migrations/02-setup.md

export default new DataSource({
    synchronize: false, // 必须关闭自动同步,否则迁移无意义
    migrations: [__dirname + "/migrations/**/*{.js,.ts}"], // glob 加载目录

    // 可选
    migrationsRun: false,          // 是否每次启动自动执行未运行迁移,默认 false
    migrationsTableName: "migrations", // 记录已执行迁移的表名,默认 "migrations"
    migrationsTransactionMode: "all",  // all | none | each
})

migration:run 后,MigrationExecutor 会负责(节选关键事实):

  1. 加载 migrations 目录下全部迁移类,通过 getMigrations() 按时间戳排序(L587);
  2. getExecutedMigrations() 对照记录表,筛出"待执行"集合 getPendingMigrations()L114-L127)——比对键是迁移的 name(即类名),所以文件名、类名、时间戳必须保持稳定,改名即被视为"新迁移"
  3. 依据 migrationsTransactionModeall 默认整批单事务 / each 每条一个事务 / none 无事务)与迁移上可选的 transaction 字段决定事务边界(相关逻辑见 L309-L357);
  4. 逐个调用 up,成功后把迁移写入记录表(insertExecutedMigration)。

因此手工创建迁移时务必记住这条链路隐含的纪律:

  • 时间戳决定顺序:不要生成多个同毫秒时间戳的迁移并期望稳定顺序;
  • 类名不可变:记录表按类名比对,改名会重复执行或失去回滚定位;
  • down 必须可用:记录表存在是 migration:revert 能精准撤销"最后一条"的前提,而 down 的质量决定回滚是否真正还原数据库。

手工创建 vs 自动生成

TypeORM 还提供 migration:generate,可根据实体与数据库的差异自动生成迁移文件,适用于结构变更来自实体代码的场景,详见 docs/docs/migrations/04-generating.md。两者定位不同:

维度 migration:create(手工) migration:generate(自动)
适用场景 数据订正、索引调整、方言特殊 SQL、多步自定义逻辑 实体字段增删改、普通结构同步
内容来源 开发者书写 比较实体元数据与数据库后生成
控制粒度 完全可控、可读性强 一次生成大批语句,需人工 review

实践中常组合使用:复杂的数据迁移与业务订正用手工 migration:create 精确编写,常规结构演进交给 migration:generate 后人工复核。若你对迁移运行后的校验与展示感兴趣,可继续阅读 docs/docs/migrations/07-status.md(查看执行状态)、docs/docs/migrations/08-faking.md(跳过执行直接标记)以及 docs/docs/migrations/09-api.md(编程式迁移 API)。

小结与最佳实践

手工创建迁移是 TypeORM 数据库演进体系的基石,核心要点可归纳为:

  1. npx typeorm migration:create <path>/<name> 生成 {TIMESTAMP}-<name>.ts 骨架;
  2. up 中书写正向变更 SQL、在 down 中书写严格对称的回滚 SQL,全部经 QueryRunner 执行;
  3. 牢记 CLI 默认产出 TS 文件,migration:run / migration:revert 需要 .js(先编译或用 ts-node 入口);
  4. 保持时间戳与类名的稳定,它们是排序与去重的唯一依据;
  5. 关闭 synchronize 并在 DataSourcemigrations 中注册目录(或具体类),随后即可用 typeorm migration:run -- -d <datasource> 将 schema 推进到目标版本。

掌握了 migration:createup / down 的写法,你就掌握了在生产数据库上"安全前进、按需后退"的基本功,可配合官方 docs/docs/migrations/ 系列文档继续深入生成、执行、回滚与状态查看的完整迁移工作流。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391