TypeORM 迁移实战:从配置到生产部署的完整指南
快速答案
- 核心结论:TypeORM 迁移是生产环境管理数据库 schema 变更的标准方案,必须关闭
synchronize并使用migration:run命令执行。 - 第一检查项:确认
synchronize: false、迁移文件路径正确(支持 glob 模式)、migrationsTableName表(默认migrations)未被意外创建。 - 最小配置:在 DataSource 配置中添加
migrations: ['./migrations/**/*{.js,.ts}']和migrationsRun: false,然后运行npx typeorm migration:run -d ./path/to/data-source.ts。 - 适用边界:适用于 Node.js 后端(NestJS/Express)搭配 PostgreSQL、MySQL、SQLite 等关系型数据库的生产项目;不适用于无 schema 变更需求的纯查询场景。
它解决什么问题
TypeORM 迁移解决的核心问题是:在已有生产数据的情况下,安全、可追溯地变更数据库 schema。当你的应用需要添加新表、修改列类型、添加索引或删除字段时,直接修改实体定义并开启 synchronize: true 会导致数据丢失或意外变更。迁移机制通过版本化的 SQL 文件,确保每次 schema 变更都可控、可回滚、可审计。
适用场景包括:
- 团队协作的中大型项目,需要版本控制数据库变更
- 已有生产数据,不能接受自动同步的不可控行为
- 需要 CI/CD 流水线自动执行数据库迁移
- 需要回滚到特定 schema 版本的场景
核心配置与参数说明
TypeORM 迁移相关的配置集中在 DataSource 或 ormconfig 中。以下是关键参数及其作用:
| 参数名 | 是否必需 | 默认值 | 说明 |
|---|---|---|---|
synchronize | 否 | false | 生产环境必须设为 false。设为 true 时,TypeORM 会根据实体定义自动同步数据库结构,这会覆盖迁移逻辑,导致迁移记录与实际 schema 不一致。 |
migrations | 是 | [] | 迁移文件路径列表,支持 glob 模式。例如 ['./migrations/**/*{.js,.ts}'] 或 [__dirname + '/migrations/**/*{.js,.ts}']。路径必须为绝对路径或以 ./ 开头的相对路径。 |
migrationsRun | 否 | false | 应用启动时是否自动运行所有待执行的迁移。生产环境建议设为 false,由 CI/CD 或手动触发,避免多实例并发执行。 |
migrationsTableName | 否 | 'migrations' | 存储已执行迁移记录的表名。可自定义,但需确保所有环境一致。 |
migrationsTransactionMode | 否 | 'all' | 控制迁移执行的事务模式。可选值:all(整个迁移过程在一个事务中)、each(每个迁移文件单独事务)、none(无事务)。 |
最小配置示例
TYPESCRIPT// data-source.ts import { DataSource } from 'typeorm'; export const AppDataSource = new DataSource({ type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT || '5432'), username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, synchronize: false, // 必须关闭 migrations: ['./migrations/**/*{.js,.ts}'], migrationsRun: false, migrationsTableName: 'typeorm_migrations', migrationsTransactionMode: 'each', // 每个迁移单独事务,减少锁表时间 });
迁移生命周期与命令
创建迁移文件
BASH# 自动生成迁移文件(基于实体与数据库的差异) npx typeorm migration:generate -d ./data-source.ts ./migrations/AddUserTable # 手动创建空迁移文件 npx typeorm migration:create ./migrations/AddUserTable
自动生成的文件会包含 up 和 down 方法,但建议检查并补充手动 SQL 逻辑。手动创建的文件需要自己编写完整的 up 和 down 方法。
执行与回滚
BASH# 运行所有待执行的迁移 npx typeorm migration:run -d ./data-source.ts # 回滚最近一次迁移 npx typeorm migration:revert -d ./data-source.ts # 查看迁移状态 npx typeorm migration:show -d ./data-source.ts
迁移文件示例
TYPESCRIPT// migrations/1700000000000-AddUserTable.ts import { MigrationInterface, QueryRunner, Table } from 'typeorm'; export class AddUserTable1700000000000 implements MigrationInterface { 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' }, { name: 'email', type: 'varchar', isUnique: true }, { name: 'created_at', type: 'timestamp', default: 'now()' }, ], }), true ); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropTable('user'); } }
生产环境实践与注意事项
1. 安全第一:关闭 synchronize
TYPESCRIPT// ❌ 危险:生产环境绝对不要这样 synchronize: true // ✅ 正确:生产环境必须 false synchronize: false
synchronize: true 会在应用启动时自动根据实体定义修改数据库结构,这可能导致:
- 意外删除列或表
- 数据丢失
- 迁移记录与数据库实际状态不一致
2. 多实例部署的迁移策略
当应用以多实例方式部署时,多个实例同时执行迁移会导致冲突。解决方案:
- 方案一:
migrationsRun: false,在 CI/CD 流水线中单独执行迁移步骤 - 方案二:使用分布式锁(如 Redis 锁)确保只有一个实例执行迁移
- 方案三:将迁移作为独立部署步骤,在启动应用前手动执行
3. 事务模式选择
TYPESCRIPT// 默认模式:整个迁移过程在一个事务中 migrationsTransactionMode: 'all' // 可能导致长时间锁表 // 推荐模式:每个迁移单独事务 migrationsTransactionMode: 'each' // 减少锁表时间 // 无事务模式:适合需要手动控制事务的复杂迁移 migrationsTransactionMode: 'none'
对于大表变更(如添加列、创建索引),建议使用 each 或 none,并配合数据库原生的非阻塞 DDL(如 PostgreSQL 的 CREATE INDEX CONCURRENTLY)。
4. 权限与安全
- 数据库用户需要具备 DDL 权限(CREATE TABLE、ALTER TABLE、DROP TABLE 等)
- 连接字符串不应硬编码在代码中,使用环境变量或密钥管理服务
- 迁移文件系统需确保读写权限,避免并发写入
常见报错与排查
错误 1:迁移已存在
Migration "migrations/1700000000000-AddUserTable" already exists in the database. Skipping.
原因:migrations 表中已记录该迁移。
解决:
- 若需重新执行,使用
typeorm migration:revert回滚 - 或手动删除
migrations表中的对应记录(谨慎操作)
错误 2:migrations 表已存在
QueryFailedError: relation "migrations" already exists
原因:首次运行迁移时,migrationsTableName 指定的表已存在。
解决:
- 确认
synchronize: false - 检查数据库是否已有遗留表
- 手动删除该表或使用
typeorm schema:drop清空(谨慎,会删除所有数据)
错误 3:连接未建立
Cannot use migrations because connection is not established.
原因:DataSource 配置错误或数据库连接参数无效。
解决:
- 检查 host、port、username、password、database 是否正确
- 确认网络连通性(防火墙、安全组等)
- 使用
typeormCLI 前确保 DataSource 文件路径正确
错误 4:路径格式错误
Path must be absolute or start with "./" for glob patterns.
原因:迁移文件路径不符合要求。
解决:
TYPESCRIPT// ✅ 正确 migrations: ['./migrations/**/*{.js,.ts}'] migrations: [__dirname + '/migrations/**/*{.js,.ts}'] // ❌ 错误 migrations: ['migrations/**/*{.js,.ts}'] migrations: ['/absolute/path/without/dot']
常见问题 FAQ
Q: 如何在生产环境中安全地回滚一个已执行的迁移?
A: 使用 typeorm migration:revert 命令。该命令会读取 migrations 表中最后一条记录,并执行对应迁移文件中的 down 方法(如果定义了)。建议在迁移文件中同时编写 up 和 down 方法,并确保 down 方法能完全撤销 up 的变更。回滚前建议备份数据库。
Q: 多个开发人员同时创建迁移文件时如何避免冲突?
A: 采用时间戳前缀命名约定(如 TIMESTAMP-first-migration.ts),并确保每个开发人员在创建迁移前从主分支拉取最新代码。使用 typeorm migration:create 命令自动生成带时间戳的文件。在 CI/CD 流程中,合并代码前运行所有迁移以验证无冲突。
Q: 迁移执行时出现死锁或长时间阻塞怎么办?
A: 将 migrationsTransactionMode 设置为 each 或 none,避免整个迁移过程在一个大事务中执行。对于大表变更(如添加列、创建索引),考虑使用 CREATE INDEX CONCURRENTLY 等非阻塞 DDL(需数据库支持)。在低峰期执行迁移,并设置合理的锁等待超时时间。
相关深度解决方案
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Prisma Migrate “Drift Detected” 错误:根因分析与修复实战。
在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Drizzle Kit `push` 命令实战:快速同步 Schema 到数据库。