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,
);
}
我在 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,
);
}