You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Sqflite调试模式首次安装未触发onUpgrade问题解决

问题原因

sqflite的回调逻辑本身没有问题:

  • 首次安装、本地无旧数据库时,只触发onCreate,要求该回调直接创建和当前版本号完全匹配的全量最新表结构
  • 本地存在低于当前版本的旧数据库时,才触发onUpgrade,要求该回调按版本号逐版本执行增量修改,把旧结构一步步升到最新版

你现在的实现有两个核心错误:

  1. 把版本迭代新增的表、字段逻辑全写在了onUpgrade里,onCreate绑定的initDB方法还是旧版本的建表逻辑,首次安装时建出来的表天生缺新版本加的结构
  2. onUpgrade的版本2分支直接调用initDB,而initDB里原本的逻辑会先删所有表再重建,老用户升级时会直接丢失全部本地数据,属于严重逻辑bug

你碰到的签名不一致卸载重装调试的场景,本质就是首次安装,自然会触发这个问题。

修复方案
  1. 先更新initDB内的所有建表SQL,把所有版本迭代新增的字段、表都补全,保证执行完initDB后直接得到和当前_databaseVersion一致的完整库结构。比如你现在版本3给chantingTable加的duration字段,直接补到chantingTable的建表语句里即可。
  2. 删掉onUpgrade里版本2分支调用initDB的错误逻辑,重写每个版本的增量升级代码,每个版本分支只写从上个版本升级到该版本需要的增量改动(新增表、加字段、数据迁移等),绝对不能在升级流程里全量删表重建。
  3. 删掉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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:09:23