阅读 201

sqlite迁移方案(migration)

背景

先说下背景,在使用electron开发一个桌面IM应用,版本迭代过程中数据库一直都是每次更新重新同步一次,具体到我使用的typeORM框架就是设置:

synchronize:true复制代码

一般情况下,当版本涉及到数据库更新,需要通过sql语句先更新数据库。本文主要是介绍下在应用更新过程中数据库同步遇到的问题,以及我后续是如何规范更新流程的;

synchronize

一开始使用'synchronize:true'其实在QA测试过程中没遇到过什么问题.虽然有考虑过更加规范的数据库更新流程,由于优先级不高,一直没时间去做。但后来把错误上报后统计,发现每周都会有几十个报错:

QueryFailedError: SQLITE_ERROR: table "" already exists复制代码

为什么没有用户反馈呢?
查了下相关的资料issueissue,主要两个原因导致:
1、entity(表)命名有大写字母;
2、每次都同步数据库(synchronize:true,不建议);
检查跟第一个原因无关。那就是第二个原因了,关键是这个问题无法重现,或者无法稳定重现。但可以理解为什么没有用户反馈,两个原因:
1、出现这个报错的原因是第二次或第二次以上同步才会出现,而我们数据库第一次同步时已更新了表,所以即使版本迭代过程中数据库有修改也不影响用户使用;
2、这个错误在出现时已被try...catch捕获(错误日志都是在这一层上报的),不会中断程序继续执行(可参考我关于错误捕获的文章);

为了解决这个问题,也顺便规范版本迭代过程中的数据库更新,我先后尝试了两种方案:
1、typeorm本身的迁移方案;
2、自己写的迁移方案;
后面介绍为什么不用typeorm的迁移方案;

typeorm migration

typeORM migration方案的流程如下:
1、修改typeorm配置,

"migrationsTableName": "migrations" // 一般不用设置,只是自己数据库中有表名和这个名字冲突时才设置,这个表会自动生成,是用来记录执行过哪些迁移脚本的(执行过的迁移脚本会生成一条记录,后面不再执行)
"migrations": ["migration/*.js"] // 指定加载迁移脚本的目录
"cli": { "migrationsDir": "migration" } // 指定迁移脚本的生成目录,但我是动态的配置文件,并不在根目录生成,所以不需要复制代码

2、生成迁移脚本;

npx typeorm migration:create -n PostRefactoring // 使用当前目录安装的typeorm来执行生成复制代码

3、 在生成的脚本中编写迁移sql语句(在up方法中写,down方法是用来revert的,暂时不了解使用场景);

// 例如我这个版本user表修改了字段名, name -> classname
public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`alter table "user" rename column "name" TO "classname";`)
}复制代码

4、执行脚本;

npx typeorm migration:run // 如果没有生成ts文件,可以通过ts-node-dev执行,e.g. ts-node-dev ./node_modules/typeorm/cli.js migration:run复制代码

这个方案有一些优点,但也有他的缺点:

// 优点
本身对每次迁移命令是否执行都做了记录(记录在migrations这个表中),不需要开发者再自己进行判断版本执行对应的迁移命令;  
// 缺点
整体流程过于繁琐和黑箱,不利于拓展和问题定位复制代码

迁移方案(自实现)

1、创建同步记录表;

// 表字段如下
id, // generate id
version, // 需要迁移的版本
executed, // 是否已执行复制代码

2、创建版本-迁移脚本映射表;

const migrationCliMap = [
  {
    version: '1.0.0',
    cli: `alter table "user" rename column "name" TO "classname";`
  },
  ...
]复制代码

3、执行迁移并添加迁移记录;

// 获取最后一条记录
let _lastMigrationCli = await _connectionManage.getConnection(_connectionManage.db).getRepository('migration')
    .query('select * from migration where executed=false order by id desc limit 1;')
let _lastMigrationCliVersion = _lastMigrationCli.version || '0.0.0'
// 遍历所有记录,从低到高将未执行的迁移命令执行后,添加到同步记录表中
for(let i=0, len=migrationCliMap.length; i < len; i+=1) {
    if(semver.gt(migrationCliMap[i].version, _lastMigrationCliVersion)) {
      await _connectionManage.getConnection(_connectionManage.db).getRepository('migration')
      .query(migrationCliMap[i].cli)
      
      await _connectionManage.getConnection(_connectionManage.db).getRepository('migration')
            .createQueryBuilder()
            .insert()
            .values({
                version: migrationCliMap[i].version,
                executed: true
            })
            .execute()
    }
}复制代码

注:当数据库尚未第一次同步时,不要执行迁移,避免执行太多迁移命令甚至出错。


作者:vb
链接:https://juejin.cn/post/7054452425519792136

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐