如何正确处理expo-sqlite中transactionAsync的约束冲突错误?
Expo-SQLite transactionAsync 未抛出约束冲突错误导致事务挂起问题
我发现expo-sqlite的transactionAsync函数存在部分错误未被抛出的问题,尤其是UNIQUE约束和重复主键引发的错误。
当执行以下代码时,第一次插入操作正常完成,但第二次插入重复主键的executeSqlAsync会一直处于挂起状态:既不返回结果也不抛出错误,事务既不回滚也不提交,最终导致数据库被锁定。
export const insertMetricGroup = async (db: SQLite.SQLiteDatabase, name: string, description: string = '', grpOrder: number = 0) => { await db.transactionAsync(async (tx) => { const idFromIncrementTable = await getAndIncreaseIncrementAsync(tx, null, 'MetricGroupTable'); console.log(idFromIncrementTable); // 第一次插入正常执行 const res1 = await tx.executeSqlAsync(`INSERT INTO MetricGroupTable (id, name, description, grpOrder) VALUES (?, ?, ?, ?)`, [idFromIncrementTable, name, description, grpOrder]); console.log(res1); // 第二次插入因主键重复本应报错,但executeSqlAsync从未返回结果(res2不会被打印) const res2 = await tx.executeSqlAsync(`INSERT INTO MetricGroupTable (id, name, description, grpOrder) VALUES (?, ?, ?, ?)`, [idFromIncrementTable, name, description, grpOrder]); console.log(res2); await metricGroupReorder(tx); }); };
getAndIncreaseIncrementAsync方法会从独立的增量表返回一个增量数值。
上述场景中,res2会一直处于待处理状态,无错误抛出也无结果返回,数据库表现为事务持续运行(被锁定),未执行回滚或提交。
在Android设备的Expo Go应用中重启后,任何插入表的尝试都会出现类似挂起情况(即使是无约束冲突的正常查询),进一步说明之前的事务无限期挂起。该问题会持续到删除数据库:
const db: SQLite.SQLiteDatabase = await SQLite.openDatabase('HabitsDb.db'); await db.closeAsync(); await db.deleteAsync();
我了解到transactionAsync曾返回结果错误或结果集,但大约一年前因相关问题进行了修改。JavaScript执行会在错误点完全停滞,这与我在其他项目中遇到的等待数据库锁释放的情况类似。
项目依赖版本如下:
"dependencies": { "@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.9.26", "expo": "~50.0.17", "expo-status-bar": "~1.11.1", "react": "18.2.0", "react-native": "0.73.6", "react-native-popup-menu": "^0.16.1", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "expo-sqlite": "~13.4.0" }
我尝试了以下低层级实现,仍出现相同结果,说明expo-sqlite库中修改返回类型的提交并非问题原因:
export const insertMetricGroupLowLevel = async(db: SQLite.SQLiteDatabase, name: string, description: string = '', grpOrder: number = 0) => { // 校验数据 if (name.length < 1) { throw('NAME TO SHORT'); } await db.execAsync([{ sql: 'BEGIN;', args: [] }], false); try { const res1: any = await getAndIncreaseIncrementAsyncLowLevel(db, 'MetricGroupTable'); await db.execAsync([{ sql: `INSERT INTO MetricGroupTable (id, name, description, grpOrder) VALUES (?, ?, ?, ?);`, args: [res1, name, description, grpOrder] }], false); await db.execAsync([{ sql: `INSERT INTO MetricGroupTable (id, name, description, grpOrder) VALUES (?, ?, ?, ?);`, args: [res1, name, description, grpOrder] }], false); await metricGroupReorderLowLevel(db); await db.execAsync([{ sql: 'END;', args: [] }], false); console.log('INSERTED METRIC GROUP') } catch(error) { await db.execAsync([{ sql: 'ROLLBACK;', args: [] }], false); console.log('FAILED INSERT OF METRIC GROUP', error); }
涉及的表结构:
CREATE TABLE IF NOT EXISTS MetricGroupTable ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, grpOrder INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS IncrementsTable ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, value INTEGER NOT NULL );
内容的提问来源于stack exchange,提问作者user22614887
相关产品推荐
相关产品推荐

