Android SQLite并发操作问题:批量插入与查询同时执行失败
Android SQLite并发插入与查询问题解决方案
问题根源分析
你的代码存在几个核心问题,导致并发操作失败:
- 手动关闭数据库连接:
SQLiteOpenHelper维护着数据库连接池,手动调用dbase.close()会破坏连接池管理,导致线程间获取连接时出现异常或锁冲突。 - SQLite默认锁机制限制:默认模式下,写事务会持有独占锁,阻塞所有读操作,直到写事务完成,这会让你的查询等待插入结束才能执行。
- 线程执行的潜在问题:如果没有正确将数据库操作放在后台线程,或者线程调度不当,也会引发操作失败或ANR。
核心解决方案
- 移除手动关闭数据库的代码:由
SQLiteOpenHelper自动管理连接池,不要在每个操作后关闭数据库实例。 - 开启WAL(写前日志)模式:开启后,写操作不会阻塞读操作,读操作可以读取数据库的快照,实现真正的并发读写。
- 确保数据库操作在后台线程执行:使用线程池或异步任务执行插入和查询,避免主线程阻塞。
修改后代码示例
1. 配置SQLiteOpenHelper开启WAL模式
public class YourDBHelper extends SQLiteOpenHelper { // ... 其他构造方法和常量定义 @Override public void onConfigure(SQLiteDatabase db) { super.onConfigure(db); db.enableWriteAheadLogging(); // 开启WAL模式,支持并发读写 } // ... onCreate、onUpgrade等方法 }
2. 修改批量插入方法
public void insertData(List<ModelClass> list) { SQLiteDatabase dbase = this.getWritableDatabase(); dbase.beginTransaction(); try { ContentValues values = new ContentValues(); for (ModelClass bokmark : list) { values.put(KEY_FILE_NAME, bokmark.getFileName()); values.put(KEY_FILE_URI, String.valueOf(bokmark.getFileUri())); values.put(KEY_FILE_PATH, String.valueOf(bokmark.getFilePath())); values.put(KEY_MONTH_NAME, bokmark.getMonthName()); values.put(KEY_DATE, bokmark.getAudioDate()); dbase.insert(TABLE_DETAIL, null, values); values.clear(); // 循环后清空ContentValues,避免重复赋值 } dbase.setTransactionSuccessful(); } finally { dbase.endTransaction(); } // 移除dbase.close(),由SQLiteOpenHelper管理连接 }
3. 修改查询方法
public ArrayList<ModelClass> getAllData() { ArrayList<ModelClass> bookmarkList = new ArrayList<>(); String selectQuery = "SELECT * FROM " + TABLE_DETAIL; SQLiteDatabase dbase = this.getReadableDatabase(); Cursor cursor = null; try { cursor = dbase.rawQuery(selectQuery, null); if (cursor.moveToFirst()) { do { ModelClass bokmarks = new ModelClass(); bokmarks.setFileName(cursor.getString(1)); bokmarks.setFileUri(Uri.parse(cursor.getString(2))); bokmarks.setFilePath(cursor.getString(3)); bokmarks.setMonthName(cursor.getString(4)); bokmarks.setAudioDate(cursor.getString(5)); bookmarkList.add(bokmarks); } while (cursor.moveToNext()); } } finally { if (cursor != null) { cursor.close(); } // 移除dbase.close() } return bookmarkList; }
线程执行示例
使用线程池同时执行插入和查询操作:
// 初始化固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(2); // 后台执行批量插入 executor.execute(() -> { yourDBHelper.insertData(yourLargeDataList); }); // 后台执行查询,查询结果回到主线程更新UI executor.execute(() -> { ArrayList<ModelClass> result = yourDBHelper.getAllData(); // 切换到主线程处理结果(Activity/Fragment中) runOnUiThread(() -> { // 在这里更新UI,比如刷新列表 }); }); // 当不再需要线程池时,记得关闭 // executor.shutdown();
关键注意事项
- WAL模式需要数据库版本支持,Android 3.0(API 11)及以上默认支持。
- 事务的使用能大幅提升批量插入的性能,同时减少锁的持有时间,降低并发冲突概率。
- 所有数据库操作必须放在后台线程,避免主线程阻塞引发ANR。
内容的提问来源于stack exchange,提问作者CodeX
相关产品推荐
相关产品推荐

