Flutter Sqflite调试模式首次安装未触发onUpgrade问题解决
问题原因
sqflite的回调逻辑本身没有问题:
- 首次安装、本地无旧数据库时,只触发
onCreate,要求该回调直接创建和当前版本号完全匹配的全量最新表结构 - 本地存在低于当前版本的旧数据库时,才触发
onUpgrade,要求该回调按版本号逐版本执行增量修改,把旧结构一步步升到最新版
你现在的实现有两个核心错误:
- 把版本迭代新增的表、字段逻辑全写在了
onUpgrade里,onCreate绑定的initDB方法还是旧版本的建表逻辑,首次安装时建出来的表天生缺新版本加的结构 onUpgrade的版本2分支直接调用initDB,而initDB里原本的逻辑会先删所有表再重建,老用户升级时会直接丢失全部本地数据,属于严重逻辑bug
你碰到的签名不一致卸载重装调试的场景,本质就是首次安装,自然会触发这个问题。
修复方案
- 先更新
initDB内的所有建表SQL,把所有版本迭代新增的字段、表都补全,保证执行完initDB后直接得到和当前_databaseVersion一致的完整库结构。比如你现在版本3给chantingTable加的duration字段,直接补到chantingTable的建表语句里即可。 - 删掉
onUpgrade里版本2分支调用initDB的错误逻辑,重写每个版本的增量升级代码,每个版本分支只写从上个版本升级到该版本需要的增量改动(新增表、加字段、数据迁移等),绝对不能在升级流程里全量删表重建。 - 删掉
initDB内冗余的DROP TABLE IF EXISTS语句——onCreate本身只会在数据库文件不存在时触发,此时本来就没有这些表,删表逻辑完全多余,还容易在误调用时造成数据丢失。
修正后的关键代码参考
// 后续每次改数据库结构,都把这个版本号+1 static const _databaseVersion = 3; Future<Database> createDatabase() async { Directory documentsDirectory = await getApplicationDocumentsDirectory(); String path = join(documentsDirectory.path, _databaseName); var database = await openDatabase(path, version: _databaseVersion, onCreate: initDB, onUpgrade: _onUpgrade); return database; } Future<void> _onUpgrade( Database database, int oldVersion, int newVersion) async { if (kDebugMode) { print("DBHelper: upgrade from $oldVersion to $newVersion"); } // 逐版本执行增量升级,不要跳版本 for (var version = oldVersion + 1; version <= newVersion; version++) { switch (version) { case 2: { // 这里写v1升v2的增量逻辑,比如v2新增了ebook表就在这里写建表语句,不要全量删表 break; } case 3: { // v2升v3加duration字段,保留原有逻辑即可 await database.execute( "ALTER TABLE $chantingTable ADD COLUMN duration INTEGER"); break; } } } } // 这里的SQL要始终保持是最新版本的全量建表逻辑 void initDB(Database database, int version) async { const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT'; const uuidType = 'TEXT PRIMARY KEY'; const textType = 'TEXT'; const integerType = 'INTEGER'; // 已删除所有DROP TABLE语句,onCreate场景本来就不存在旧表 // 给chanting表补上版本3新增的duration字段 String sql = "CREATE TABLE $chantingCatalogTable (" "id $uuidType, " "title $textType, " "sort_order $integerType, " "active $integerType, " "created_at $integerType, " "updated_at $integerType" ")"; await database.execute(sql); sql = "CREATE TABLE $chantingTable (" "id $uuidType, " "chanting_catalog_id $textType, " "title $textType, " "text $textType, " "file_name $textType, " "sort_order $integerType, " "active $integerType, " "created_at $integerType, " "updated_at $integerType, " "duration $integerType, " "FOREIGN KEY (chanting_catalog_id) REFERENCES $chantingCatalogTable (id)" ")"; await database.execute(sql); sql = "CREATE TABLE $favouriteTable (" "id $idType, " "name $textType NOT NULL, " "is_default $integerType, " "UNIQUE(name)" ")"; await database.execute(sql); sql = "CREATE TABLE $songTable (" "id $textType NOT NULL, " "favouriteId $integerType NOT NULL, " "task_id $textType, " "album $textType, " "title $textType, " "artist $textType, " "artUrl $textType, " "audioUrl $textType, " "duration $integerType, " "is_favourite $integerType, " "is_downloaded $integerType, " "sort_order $integerType, " "PRIMARY KEY(id, favouriteId)" ")"; await database.execute(sql); try { sql = "INSERT INTO $favouriteTable (name, is_default)" " VALUES (?,?)"; await database.execute(sql, ["Downloaded", 1]); } catch (e) { if (kDebugMode) { print(e); } } sql = "CREATE TABLE $ebookTable (" "id $uuidType, " "taskId $integerType, " "title $textType, " "url $textType, " "thumbnail $textType, " "monk_name $textType, " "monk_image_url $textType" ")"; await database.execute(sql); }
后续迭代规范
之后每次改数据库结构,固定做三个操作即可:
- 把
_databaseVersion数值加1 - 同步更新
initDB里的建表SQL,保证新用户首次安装直接得到最新结构 - 在
onUpgrade里新增对应版本的增量升级逻辑,保证老用户升级时能一步步把旧结构更新到最新,不丢本地数据
改完后不管是首次安装、覆盖升级、还是卸载重装的场景,数据库结构都会保持正确。
内容的提问来源于stack exchange,提问作者Alex Aung
相关产品推荐
相关产品推荐

