拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TypeORM 迁移(Migrations)实战指南:生产环境下安全地同步数据库结构变更

TypeORM 迁移Migrations实战指南生产环境下安全地同步数据库结构变更【免费下载链接】typeormTypeScript JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeorm在开发环境TypeORM 可以让实体自动同步到数据库但当项目上线、数据库中积累了真实数据后这种“一把梭”的自动同步将变得极度危险。本文将以当前仓库 typeorm 的官方迁移文档docs/docs/migrations/01-why.md为主线讲解为什么生产环境必须使用 Migration迁移、一个迁移文件究竟由什么构成、如何用一条 SQL 完成一次典型的“改列名”上线并结合仓库源码拆解 TypeORM 迁移的底层工作方式。读完本文你将掌握从“何时需要迁移”到“如何写出第一个可上线的迁移”的完整思路并能据此设计自己的数据库版本演进方案。为什么一旦上线synchronize: true就不再安全TypeORM 提供了synchronize选项它会在每次应用启动时根据实体元数据自动把数据库 Schema 同步成最新状态。在开发期这个特性非常方便——改完实体、重启服务表结构就跟着变了。但一旦进入生产环境情况就完全不同了。官方文档docs/docs/migrations/01-why.md给出的判断非常明确Typically, it is unsafe to usesynchronize: truefor schema synchronization on production once you get data in your database.也就是说当数据库中开始有真实数据后用synchronize: true做 Schema 同步通常是不安全的。原因很直观自动同步会基于实体“推断”出需要执行的 DDL你无法控制它何时、以何种顺序执行也无法在变更前先做数据备份、数据清洗或平滑迁移一次粗心的同步可能直接触发DROP COLUMN、重建表等破坏性操作导致线上数据丢失同步是“隐式”发生的运维与后续开发者难以追溯某次结构变更究竟是谁、在哪个版本、以什么 SQL 触发的审计与回滚无从谈起。从源码实现可以更清楚地看到synchronize的行为方式。在 DataSource.ts 中建立连接时会判断this.options.synchronize是否为真若为真则自动调用this.synchronize()直接基于实体与数据库当前结构的比对结果执行同步。它背后走的是 RdbmsSchemaBuilder 那套“全量比对、全量修补”的逻辑对开发库友好但对生产库是一场豪赌。这正是 Migration 登场的时机——它是官方推荐、面向生产环境的 Schema 变更管理手段。什么是 Migration一个携带 SQL 的文件迁移的定义并不神秘。文档中的原文定义是A migration is just a single file with SQL queries to update a database schema and apply new changes to an existing database.即迁移就是一个携带 SQL 查询的文件用来更新数据库 Schema把新的结构变更应用到已有的数据库上。它把“数据库结构演进”这件事从 ORM 的自动推断转变成开发者手写、可审查、可版本化、可追溯的显式脚本。在 TypeORM 中一个迁移文件对应一个类这个类必须实现MigrationInterface。我们可以直接在源码中查看该接口的完整契约src/migration/MigrationInterface.tsexport interface MigrationInterface { /** * Optional migration name, defaults to class name. */ name?: string /** * Optional flag to determine whether to run the migration in a transaction or not. */ transaction?: boolean /** * Run the migrations. */ up(queryRunner: QueryRunner): Promiseany /** * Reverse the migrations. */ down(queryRunner: QueryRunner): Promiseany }也就是说一个迁移类需要实现两个方法方法作用up(queryRunner)执行迁移把数据库从当前版本升级到新版本写“前进”的 SQLdown(queryRunner)回滚迁移撤销up所做的更改写“后退”的 SQL两个方法都能拿到一个QueryRunner对象所有数据库操作都通过它来执行。与此同时src/migration/Migration.ts 中的Migration类描述了迁移在数据库中的“档案记录”包含id执行顺序、timestamp时间戳用于排序、name类名、instance迁移实例与transaction是否在事务中执行等字段。可见 TypeORM 对迁移的管理是“元数据 文件 数据库记录”三者结合的体系这一点会在后文继续展开。第一个迁移案例给已有生产库的列改名纸上得来终觉浅文档用一个非常贴切的实战场景说明了迁移的完整价值你有一个已经运行数月的生产数据库与对应的Post实体import { Entity, Column, PrimaryGeneratedColumn } from typeorm Entity() export class Post { PrimaryGeneratedColumn() id: number Column() title: string Column() text: string }这张post表在线上稳定运行里面存着成千上万条帖子。现在业务方要求发布一个新版本把title这一列改名为name。请问你要怎么做如果直接改实体并打开synchronizeTypeORM 有可能通过删表重建或风险不可控的方式完成任务在真实数据面前无异于“裸奔”正确的做法是写一个迁移文件用一条标准的 DDL 完成列的重命名。以 PostgreSQL 方言为例ALTER TABLE post RENAME COLUMN title TO name;执行这条 SQL 之后数据库 Schema 就已经准备好与你的新代码配合了。TypeORM 提供的 “migrations” 机制正是让你有这样一个受控的“容器”把这类 SQL 写下来并在你需要的时候发布流程中可靠地执行。对应的迁移文件整体形态如下import { MigrationInterface, QueryRunner } from typeorm export class PostRefactoringTIMESTAMP implements MigrationInterface { async up(queryRunner: QueryRunner): Promisevoid { await queryRunner.query( ALTER TABLE post RENAME COLUMN title TO name, ) } async down(queryRunner: QueryRunner): Promisevoid { await queryRunner.query( ALTER TABLE post RENAME COLUMN name TO title, ) // 撤销 up 方法中做的修改 } }注意两个要点up与down必须是互逆的up把title改名成namedown就必须把name改回title。这样才能保证迁移可回滚down用于撤销最近一次迁移是灾难恢复与版本回退的保险丝。从哪来、去哪存手动创建迁移文件在真实项目中迁移文件通常由 TypeORM CLI 生成骨架你再填充具体的 SQL 逻辑。官方文档docs/docs/migrations/03-creating.md给出了手动创建的方式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}是生成时刻的时间戳。之所以用时间戳作为文件名前缀是因为 TypeORM 需要依据时间戳决定迁移的执行顺序参见前文 Migration.ts 中的timestamp字段。打开这个文件你会看到 MigrationCreateCommand 为你生成的骨架——一个实现了up/down的空迁移类等待你填入迁移逻辑。可见迁移的本质是“时间戳命名 成对的前进/回滚方法”这让每次结构变更都成为一个独立的、可排序的、可回滚的发布单元。交给 DataSource迁移工作的“总开关”写好迁移文件后还需要在 DataSource 中把它“挂载”到 TypeORM 的运行体系里。官方迁移系列的配置文档docs/docs/migrations/02-setup.md给出了标准配置模板export default new DataSource({ // 基础设置 synchronize: false, migrations: [__dirname /migrations/**/*{.js,.ts}], // 可选设置 migrationsRun: false, migrationsTableName: migrations, migrationsTransactionMode: all, // 其他选项…… })下面逐一说明这些选项的含义并补充源码中的取值细节。synchronize必须关闭正如本文第一节强调的想用迁移管理结构演进第一步就是关闭自动同步否则迁移会失去意义。在 BaseDataSourceOptions.ts 中该选项的注释也明确警告不要在生产环境使用它否则可能丢失生产数据。migrations告诉 TypeORM 去哪里找迁移该选项接受“迁移类”和“迁移文件目录”两种形式源码注释见 BaseDataSourceOptions.ts。最省心的方式是传入目录 glob 通配符让 TypeORM 自动加载目录下的全部迁移文件migrations: [__dirname /migrations/**/*{.js,.ts}]同时声明.js与.ts两种扩展名有一个实际好处开发环境可以直接跑 TypeScript 源码而生产环境例如打包进 Docker 镜像时可以运行编译后的 JavaScript两者共用同一套配置。如果你希望获得更精细的控制也可以显式导入具体的迁移类import FirstMigration from ./migrations/TIMESTAMP-first-migration import SecondMigration from ./migrations/TIMESTAMP-second-migration export default new DataSource({ migrations: [FirstMigration, SecondMigration], })代价是每次新增迁移都要手动改这段代码文档提示这种方式“需要更多手工操作也更容易出错”因此日常开发推荐 glob 目录方式。migrationsRun是否随应用启动自动执行如果设为true每次应用启动时 TypeORM 都会自动执行尚未跑过的迁移默认值为false源码见 BaseDataSourceOptions.ts。在需要“启动即就绪”的部署形态如无单独迁移步骤的 PaaS 平台下很实用否则通常交给 CI/CD 或 CLI 显式执行。migrationsTableName迁移记录表的名字TypeORM 会把已经执行过的迁移登记在一张专门表里默认表名就是migrations。你可以按需改名例如migrationsTableName: some_custom_migrations_tablemigrationsTransactionMode迁移的事务模式该选项控制迁移执行时的事务边界可选值见 BaseDataSourceOptions.ts含义如下取值行为all默认把一次要执行的所有迁移包进单个事务none不使用事务执行迁移each每条迁移各自运行在独立事务中在MigrationInterface中每个迁移类还可以通过自身的transaction属性覆盖全局行为仅当全局模式为each或none时可覆盖这为特殊场景比如某条 DDL 在事务外执行更稳妥留出了弹性空间。手写太累让 TypeORM 帮你生成迁移需要强调一点像“改列名”这样的迁移很多时候你根本不需要手写 SQL。TypeORM 提供了自动生成能力它会把你实体中做的修改与服务器上现有的数据库 Schema 做比对然后自动产出迁移文件并写清所需的全部 SQL。官方文档docs/docs/migrations/04-generating.md中展示了核心命令typeorm migration:generate -d path/to/datasource migration-name-d指定了 DataSource 实例定义所在的路径。假如你刚把Post实体的title属性改名成name执行生成命令后TypeORM 会产出{TIMESTAMP}-post-refactoring.ts其up中自动包含类似下面的 SQLALTER TABLE post ALTER COLUMN title RENAME TO name而down自动写出方向相反的重命名语句形成可回滚的对称结构。若检测不到任何 Schema 变化命令会以退出码1结束提示你无需生成。文档给出的经验法则是每次修改模型之后都生成一次迁移让结构变更始终以迁移文件的形态沉淀下来。可见“迁移 手写 自动生成”双轨并行前者适合复杂的数据修补后者适合常规的模型演进。结合源码理解迁移的整体工作流把以上内容串起来TypeORM 的迁移机制在源码层面可以归纳为一条清晰的链路加载通过migrations选项glob 或类数组在 DataSource 初始化时加载迁移类实例化与排序每个迁移被包装为带id、timestamp、name、instance的 Migration 记录按时间戳决定执行顺序登记已执行的迁移会被写入migrationsTableName指定的记录表默认migrations下次执行时跳过已完成项执行与回滚运行迁移时调用迁移实例的up(queryRunner)回滚时调用down(queryRunner)up/down中的一切 SQL 都经由QueryRunner提交给数据库接口定义见 MigrationInterface.ts事务保障由migrationsTransactionModeall/none/each与单个迁移的transaction标志共同决定事务边界保证迁移要么全部生效、要么按预期粒度可控回滚。这也回答了最开始的疑问为什么说 Migration 是生产环境结构变更的标准答案——因为对比synchronize的“自动、隐式、不可审计”迁移做到了“显式、可审查、可排序、可回滚、可记录”。小结生产库存在真实数据后应关闭synchronize: true改用 Migration 管理 Schema 演进一个迁移 一个携带 SQL 的文件 实现up/down两个方法的类前者前进、后者回滚二者互逆以“改列名”为代表的任何结构变更都可以写成一条或一组可执行的 DDL交给 TypeORM 的迁移机制在发布时受控执行迁移文件用npx typeorm migration:create手工起步、用typeorm migration:generate根据实体差异自动生成在 DataSource 中正确配置synchronize、migrations、migrationsRun、migrationsTableName、migrationsTransactionMode迁移体系即告就绪。本文所在的官方文档还包含更完整的迁移专题章节迁移的执行05-executing.md、回滚06-reverting.md、查看状态07-status.md、伪造迁移08-faking.md与完整 API09-api.md读者可以按需继续深入。【免费下载链接】typeormTypeScript JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeorm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门