TypeORM 迁移实战:从配置到生产部署的完整指南

主题: typeorm-migration-table-already-exists更新于: 2026/7/30作者:AgentFactory 技术团队

快速答案

  • 核心结论: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 迁移相关的配置集中在 DataSourceormconfig 中。以下是关键参数及其作用:

参数名是否必需默认值说明
synchronizefalse生产环境必须设为 false。设为 true 时,TypeORM 会根据实体定义自动同步数据库结构,这会覆盖迁移逻辑,导致迁移记录与实际 schema 不一致。
migrations[]迁移文件路径列表,支持 glob 模式。例如 ['./migrations/**/*{.js,.ts}'][__dirname + '/migrations/**/*{.js,.ts}']。路径必须为绝对路径或以 ./ 开头的相对路径。
migrationsRunfalse应用启动时是否自动运行所有待执行的迁移。生产环境建议设为 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

自动生成的文件会包含 updown 方法,但建议检查并补充手动 SQL 逻辑。手动创建的文件需要自己编写完整的 updown 方法。

执行与回滚

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'

对于大表变更(如添加列、创建索引),建议使用 eachnone,并配合数据库原生的非阻塞 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 是否正确
  • 确认网络连通性(防火墙、安全组等)
  • 使用 typeorm CLI 前确保 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 方法(如果定义了)。建议在迁移文件中同时编写 updown 方法,并确保 down 方法能完全撤销 up 的变更。回滚前建议备份数据库。

Q: 多个开发人员同时创建迁移文件时如何避免冲突?

A: 采用时间戳前缀命名约定(如 TIMESTAMP-first-migration.ts),并确保每个开发人员在创建迁移前从主分支拉取最新代码。使用 typeorm migration:create 命令自动生成带时间戳的文件。在 CI/CD 流程中,合并代码前运行所有迁移以验证无冲突。

Q: 迁移执行时出现死锁或长时间阻塞怎么办?

A: 将 migrationsTransactionMode 设置为 eachnone,避免整个迁移过程在一个大事务中执行。对于大表变更(如添加列、创建索引),考虑使用 CREATE INDEX CONCURRENTLY 等非阻塞 DDL(需数据库支持)。在低峰期执行迁移,并设置合理的锁等待超时时间。

相关深度解决方案

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Prisma Migrate “Drift Detected” 错误:根因分析与修复实战

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Drizzle Kit `push` 命令实战:快速同步 Schema 到数据库