下面的内容采用 NestJS + Typeorm 为案例,讲解在开发环境的项目上线后,调整数据库结构与生产环境不同步时,如何正确的解决。
开发环境
ORM框架 synchronize 的错误使用
作者推荐,synchronize 只有在如下场景时,才会开启,其它时候都是关闭的。
- 项目初始阶段开发时
此时的项目刚开始开发,结构经常变动
- 本地环境数据库的是空的,需要同步entity结构
也可以使用 migration 同步
- 大的需求调整需要频繁的更改结构
记得一句话:migration 迁移原则上不会破坏原有的数据,但是比较慢。如果不担心数据被破坏,且在短时间内需要经常打改变 entity 的时候,才开启 synchronize
如何配置
- 创建 migration 连接文件,这个文件只会在执行迁移迁移脚本时被执行
ts import * as dotenv from 'dotenv';
import { resolve } from 'node:path';
import { DataSource } from 'typeorm';
const isProduction = process.env.NODE_ENV === 'production';
dotenv.config({
path: [
resolve(__dirname, '../../../.env'),
resolve(
__dirname,
`../../../.env.${isProduction ? 'production' : 'development'}`,
),
],
});
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT as string, 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: false,
logging: true,
entities: [resolve(__dirname, '../../module/**/*.entity{.ts,.js}')], // 指定 entity 文件路径
migrations: [resolve(__dirname, '../../migrations/*{.ts,.js}')], // 指定 migration 脚本文件路径
});
- 比对差异sql,并生成 sql 脚本
执行原理:先使用 migration 脚本连接数据库,然后比对 entities 的 entity,来生成 sql 脚本。因此每次在开发环境下修改了数据库结构,就需要执行 sync 命令。
json{
// 连接 migrate 数据库
"typeorm": "npx ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -d src/databases/postgres/migration.ts",
// 执行 migrate 生成的 diff 脚本
"migration:dev": "npm run typeorm -- migration:run",
// 先删除 dist,随后执行数据库连接,然后生成差异sql,然后在执行 diff 脚本
"sync": "rimraf dist && npm run typeorm -- migration:generate src/migrations/change-db && npm run migration:dev",
// 生产环境同步(生产环境可能只有node环境)
"sync:prod": "node ./node_modules/typeorm/cli.js -d dist/databases/postgres/migration.js migration:run"
}
生产环境
生产环境的 synchronize 一定是关闭的,千万不能开启。
上线部署
按照上述的操作,开发环境生成了很多migration 脚本,那么生产环境如何执行呢?
- migration 脚本和(项目/git仓库)同步
开发环境完成后,需要把 migration 的diff脚本推送到git仓库,并且同步到生产环境服务器上,这样才是标准的做法。
- 生产环境的启动更改
可能启动项目时,执行:npm run start、或者是 build 后执行 app.js。但是在执行前都需要执行一次 migration diff 脚本保证生产环境的数据库结构正确。
yml ## 例如,如果是 docker 启动时,就需要做如下调整:
CMD ["sh", "-c", "npm run migration:prod && node dist/main.js"]
- 生产环境启动的前置操作
原则上执行 migration 的 diff 脚本是不会出现问题的,但是为了防范,还是要在执行前,备份当前的数据库。
sh ## docker 为例,先执行如下命令:
docker exec -i <容器名称/ID> /usr/local/bin/pg_dump -U <数据库连接用户> -d <数据库名称> -F c > /backup/$(date +"%Y%m%d_%H%M%S")_backup.dump
## /backup/$(date +"%Y%m%d_%H%M%S")_backup.dump:数据库的备份文件名称,但是要考虑数据库的体量、运行状态、服务器空间等问题。这里不做多余的赘述。
小白解惑
- migration 在生产环境为什么不需要执行生成 diff 脚本的操作?
因为在正常逻辑下,migration 一定是在开发环境中都生成好了,在开发环境中直接执行就好了,没必要生成。
- 会不会出现diff脚本被多次执行的问题?
不会,因为每次执行diff脚本时,都会在数据库新增一张migration的表,这个表会记录以及执行过的 diff 脚本。因此不用担心重复执行。(存在可操作空间)
- migration 生成的 diff 脚本过多怎么办?
不需要担心,diff 脚本本身也是一种备份,可以被版本还原的。放在项目和git仓库中一定是最合适的。但是注意:不要把diff脚本放到 master 分支中。
迁移本方案流程
有可能读者之前的 synchronize 一直是开启的,如果想要切换到该方案来执行,需要如何做
- 备份当前的线上数据库
把当前的线上数据库进行备份,一定要保持结构。随后在新建一个空的数据库,把备份导入到这个空的数据库中。
- 修改migration 连接脚本
把 migration 的连接脚本指向这个新建的数据库中。
- 执行diff脚本的同步
执行 sync 命令,这样就会生成和生产环境数据库diff的脚本。
- 执行上线部署
根据:开发环境 - 上线部署的章节执行。注意:一旦这么做之后,同时也意味着,你最初的开发环境数据库将不在需要,后续的操作将都会在新的数据库中完成。