提要
- 之前开坑实现了electron中使用better-sqlite3,只完成了使用以及基础配置
- 后面有个新的场景,就是后续客户端版本升级,当更新内容涉及表结构变化时,需要同时更新客户端的库表,然后还不能影响到客户的历史数据,之前想到的是用knex但是一直没操作成功(一直报错来着)。
- 后面想了几种场景,发现用knex有点复杂了,其实从数据库层面来看,可以根据版本变化直接执行更新的sql语句,来进行更新。
具体操作
创建数据库时同时创建数据库版本,根据手动控制版本变化选择是否执行字段或者库表更新sql。下面是实际操作流程。

- 先创建一个migrations的文件夹,该文件夹下存放更新用的sql。
- 约定该文件夹下sql的名字为1.sql …2.sql 文件名即为当前数据库的版本号。(后面我又弄了一个json来做管理,感觉比多文件要省事儿)目录结构如下。

- 初始化一张版本控制表,记录当前应用数据库版本信息。可以放到schema.sql里随版本初始化一起生成,或者对现有应用改造的话就在代码里db连上之后生成。
CREATE TABLE IF NOT EXISTS migrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, version INTEGER NOT NULL, description TEXT, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(version) ); - 所有的历史版本sql一定不能删除或者修改!!因为后面更新要按版本顺序更新库表。修改会导致不同版本的客户端出现问题! 所以涉及数据库的变化一定在开发环境测试好再上线!!
- 代码就不写了,大致思路就是获取目录下的文件名和数据库里最新的version对比,如果文件名比库里的version大就执行一下sql文件,既可完成sql的更新。注意一点要把更新的sql文件打到包里 package.json里配置 data/migrations/*.sql

改进
前面的文件依赖创建迁移的sql文件,如果更新的频繁会有很多个sql文件,目前想到改进的方案是创建一个json文件,然后通过读取json文件来控制版本升级,这样就一个json文件就行了。以下是核心代码,思路还是把json的内容读出来,按照约定好的格式进行解析,执行sql更新。
{
"version-2": {
"description": "测试更新2",
"sqls": ["CREATE TABLE IF NOT EXISTS detail (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL,description TEXT,price REAL NOT NULL );"]
},
"version-1": {
"description": "测试更新1",
"sqls": ["ALTER TABLE users ADD COLUMN phone TEXT;", "ALTER TABLE products ADD COLUMN phone TEXT;"]
}
}
// 初始化迁移表并获取当前版本
private async initializeMigrations(): Promise<void> {
if (!existsSync(this.migrationDir)) {
mkdirSync(this.migrationDir, { recursive: true });
logger.info(`迁移目录已创建: ${this.migrationDir}`);
}
this.checkAndApplyMigrations();
}
// 检查并应用迁移
private checkAndApplyMigrations(): void {
const currentVersion = this.getCurrentVersionFromDB();
const migrations = this.getMigrationFilesFromJson();
logger.info(`test: ${JSON.stringify(migrations)}`);
logger.info(`当前数据库版本: ${currentVersion}`);
// 筛选出版本号高于当前版本的迁移文件
const pendingMigrations = migrations.filter((m: any) => {
return m.version > currentVersion;
});
logger.info(`pendingMigrations${JSON.stringify(pendingMigrations)}`);
if (pendingMigrations.length === 0) {
logger.info('数据库已是最新版本,无需迁移');
return;
}
logger.info(`发现 ${pendingMigrations.length} 个待应用迁移`);
// 按版本号升序执行迁移
const sortedMigrations = [...pendingMigrations].sort((a, b) => a.version - b.version);
// 使用事务执行所有迁移
const transaction = this.db.transaction(() => {
sortedMigrations.forEach((migration) => {
try {
if (migration.sqls && migration.sqls.length > 0) {
migration.sqls.forEach((sql) => {
this.db.exec(sql);
});
this.recordMigration(this.db, migration.version, `迁移记录成功 - 版本: ${migration.version},说明: ${migration.description}`);
}
} catch (error) {
logger.error(`迁移失败 ${migration.version}.sql:`, error);
throw error; // 回滚事务
}
});
});
transaction();
logger.info('所有迁移已完成');
}
private getMigrationFilesFromJson() {
const migrationPath = resolve(this.migrationDir, 'update.json');
if (!existsSync(migrationPath)) {
logger.info('未找到迁移文件: update.json');
return [];
}
const migrationContent = readFileSync(migrationPath, 'utf8');
const migrationData = JSON.parse(migrationContent);
// 提取所有版本键并按数字排序
const versionKeys = Object.keys(migrationData)
.filter((key) => key.startsWith('version-'))
.sort((a, b) => {
const numA = parseInt(a.split('-')[1], 10);
const numB = parseInt(b.split('-')[1], 10);
return numA - numB;
});
return versionKeys.map((key, index) => {
return { ...migrationData[key], version: index + 1 };
});
}
结论
- 目前只尝试了一些简单的更新,类似增加字段,增加库表等操作。更复杂的操作,类似修改字段类型,修改表结构等操作还没具体尝试。要多注意处理好执行错误还有兜底。开发环境多进行验证。
- 大家有更好,更完善的做法欢迎批评指正
2187

被折叠的 条评论
为什么被折叠?



