Flutter with SQLite - 事务中的约束违规导致后续事务出错

Flutter with SQLite - constraint violation in transaction leads to error in following transaction

我在 sqflite 包中使用 Flutter。

我在事务中执行了违反 FOREIGN KEY 约束的查询。

我捕获异常,然后尝试启动另一个事务。此 DatabaseException 失败:cannot start a transaction within a transaction.

这是一个可重现性最低的示例:

/////////////////////////////
/////////////////////////////
//
// Create/open the database
//
/////////////////////////////
/////////////////////////////
String databasePath = await getDatabasesPath();
String finalPath = join(databasePath, "testsqlite.db");
Database db = await openDatabase(finalPath,
  onCreate: (Database _db, int version) async {
    await _db.transaction((Transaction trx) async {
      await trx.execute("CREATE TABLE User(id INTEGER PRIMARY KEY, age INTEGER);");
      await trx.execute("""
        CREATE TABLE Purchase(
          id INTEGER PRIMARY KEY,
          userId INTEGER REFERENCES User(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
        );
      """);
    });
  },
  onOpen: (Database db) async {
    await db.execute("PRAGMA foreign_keys = ON");
  },
  version: 1,
);


/////////////////////////////
/////////////////////////////
//
// ACTION!
//
/////////////////////////////
/////////////////////////////
try {
  await db.transaction((Transaction trx) async {
    // Run query to cause SQLITE_CONSTRAINT_FOREIGNKEY
    await trx.execute("INSERT INTO Purchase(id, userId) VALUES(1, 1);");
  });
}
catch (ex) {
  print("Correctly crashed on SQLITE_CONSTRAINT_FOREIGNKEY");

  try {
    // Try to start a new transaction
    await db.transaction((Transaction trx) async {

    });
  }
  catch (ex2) {
    print("Incorrectly crashed on 'cannot start a transaction within a transaction'");
  }
}

预计 await trx.execute("INSERT INTO Purchase(id, userId) VALUES(1, 1);"); 会出现异常。

预计不会出现以下 await db.transaction((Transaction trx) async 异常。

我试图在 SQLITE_CONSTRAINT_FOREIGNKEY 异常之后显式添加 await db.execute("ROLLBACK");,但我得到另一个异常,说没有活动事务。

我还尝试稍后开始第二笔交易(例如,5 秒后使用 Timer)- 并遇到了同样的异常。

为什么尝试启动第二个事务会抛出异常?

如何在 SQLITE_CONSTRAINT_FOREIGNKEY 异常后开始新交易?

我一直在 Android 11 上重现(我没有尝试过其他版本)。

this answer开始,解决办法是关闭数据库再打开。

您可以将打开数据库的代码提取到一个方法中,以便在遇到错误时调用它,以便再次打开数据库。

/////////////////////////////
/////////////////////////////
//
// Create/open the database
//
/////////////////////////////
/////////////////////////////
    String databasePath = await getDatabasesPath();
    String finalPath = join(databasePath, "testsqlite.db");
    Database db = await getDatabase(finalPath);

/////////////////////////////
/////////////////////////////
//
// ACTION!
//
/////////////////////////////
/////////////////////////////
    try {
      await db.transaction((Transaction trx) async {
        // Run query to cause SQLITE_CONSTRAINT_FOREIGNKEY
        await trx.execute("INSERT INTO Purchase(id, userId) VALUES(1, 1);");
      });
    } catch (ex) {
      print("Correctly crashed on SQLITE_CONSTRAINT_FOREIGNKEY");

      try {
        db.close();
        db = await getDatabase(finalPath);
        // Try to start a new transaction
        await db.transaction((Transaction trx) async {});
      } catch (ex2) {
        print(
            "Incorrectly crashed on 'cannot start a transaction within a transaction'");
      }
    }
  }

  Future<Database> getDatabase(String finalPath) async {
    return await openDatabase(
      finalPath,
      onCreate: (Database _db, int version) async {
        await _db.transaction((Transaction trx) async {
          await trx.execute(
              "CREATE TABLE User(id INTEGER PRIMARY KEY, age INTEGER);");
          await trx.execute("""
      CREATE TABLE Purchase(
        id INTEGER PRIMARY KEY,
        userId INTEGER REFERENCES User(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
      );
    """);
        });
      },
      onOpen: (Database db) async {
        await db.execute("PRAGMA foreign_keys = ON");
      },
      version: 1,
    );
  }