如何优化Flutter中sqflite数据库插入、查询操作的运行速度?
sqflite 本地数据库性能优化方案
现有代码直接导致性能问题的核心缺陷
- 未缓存数据库实例:所有增删改查方法每次调用都会重新执行
openDatabase流程,数据库打开本身存在磁盘IO校验、文件句柄创建开销,高频操作下这部分无意义耗时占比极高 - 异步操作缺失await:
insertNote的db.insert、updateNote的batch.commit()都没有加await关键字,看似操作返回快,实际是操作在后台排队等待执行,很容易引发数据库锁竞争、操作堆积,反而拉长整体响应时间 - 建表批量逻辑完全失效:建表时创建了batch对象,但三个建表语句都是直接用txn.execute执行,最后调用的
batch.commit()是空提交,完全没用到批量事务减少IO次数的特性 - 缺失关联字段索引:
event、note表都通过habitId和主表habitTable关联,目前没有给habitId建索引,所有按习惯筛选数据、关联查询的场景都会触发全表扫描,数据量超过几百条之后查询耗时会明显上涨 - 全表无差别拉取:所有
query调用都没有加筛选条件、分页参数,哪怕只需要单条数据也会把整张表的所有字段、所有记录全部加载到内存,表数据量越大,序列化、内存拷贝的耗时越高 - 单条更新滥用batch:
updateNote仅更新单条记录就创建batch对象,额外增加了对象创建、事务调度的开销
优化后可直接使用的代码实现
import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart' as sqf; class DataBaseHelper { // 全局单例缓存数据库实例,仅初始化一次 static sqf.Database? _dbInstance; static Future<sqf.Database> database() async { if (_dbInstance != null) return _dbInstance!; final dbPath = await sqf.getDatabasesPath(); _dbInstance = await sqf.openDatabase( join(dbPath, 'habits_record.db'), onCreate: (db, version) async { final batch = db.batch(); // 建表语句统一走批量提交 batch.execute(''' CREATE TABLE habitTable( id INTEGER PRIMARY KEY, title TEXT, reason TEXT, plan TEXT, iconData TEXT, hour INTEGER, minute INTEGER, notificationText TEXT, notificationId INTEGER, alarmHour INTEGER, alarmMinute INTEGER ) '''); batch.execute(''' CREATE TABLE event( id TEXT PRIMARY KEY, dateTime TEXT, habitId INTEGER ) '''); // 给外键关联字段加索引,避免全表扫描 batch.execute('CREATE INDEX idx_event_habitId ON event(habitId)'); batch.execute(''' CREATE TABLE note( id TEXT PRIMARY KEY, dateTime TEXT, habitId INTEGER, noteContent TEXT ) '''); batch.execute('CREATE INDEX idx_note_habitId ON note(habitId)'); // noResult设为true,跳过操作结果回传,减少不必要的内存开销 await batch.commit(noResult: true); }, version: 1, ); return _dbInstance!; } static Future<void> insertNote(Map<String, Object> data) async { final db = await database(); // 补全await,避免操作堆积引发锁库 await db.insert('note', data, conflictAlgorithm: sqf.ConflictAlgorithm.replace); } static Future<void> deleteNote(String id) async { final db = await database(); await db.delete( 'note', where: 'id = ?', whereArgs: [id], ); } /// 按需查询笔记,支持按习惯ID筛选、分页拉取 static Future<List<Map<String, dynamic>>> fetchAndSetNotes({ int? habitId, int? page, int pageSize = 20 }) async { final db = await database(); String? whereClause; List<Object?>? whereArgs; if (habitId != null) { whereClause = 'habitId = ?'; whereArgs = [habitId]; } return await db.query( 'note', where: whereClause, whereArgs: whereArgs, limit: page != null ? pageSize : null, offset: page != null ? (page - 1) * pageSize : null, orderBy: 'dateTime DESC' ); } static Future<void> updateNote(Map<String, Object> newNote) async { final db = await database(); // 单条更新直接调用update方法,无需创建batch await db.update( 'note', newNote, where: 'id = ?', whereArgs: [newNote['id']], ); } static Future<void> insertEvent(Map<String, Object> data) async { final db = await database(); await db.insert('event', data, conflictAlgorithm: sqf.ConflictAlgorithm.replace); } static Future<void> deleteEvent(String id) async { final db = await database(); await db.delete( 'event', where: 'id = ?', whereArgs: [id], ); } // 修正原方法名拼写错误,支持按习惯ID筛选 static Future<List<Map<String, dynamic>>> fetchEvent({int? habitId}) async { final db = await database(); return await db.query( 'event', where: habitId != null ? 'habitId = ?' : null, whereArgs: habitId != null ? [habitId] : null, orderBy: 'dateTime DESC' ); } static Future<void> insertHabit(Map<String, Object> data) async { final db = await database(); await db.insert('habitTable', data, conflictAlgorithm: sqf.ConflictAlgorithm.replace); } static Future<List<Map<String, dynamic>>> fetchHabits() async { final db = await database(); return await db.query('habitTable'); } static Future<void> deleteHabit(int id) async { final db = await database(); // 删主表数据时连带清理关联表数据,避免脏数据占用空间,用事务保证操作原子性 await db.transaction((txn) async { await txn.delete('habitTable', where: 'id = ?', whereArgs: [id]); await txn.delete('event', where: 'habitId = ?', whereArgs: [id]); await txn.delete('note', where: 'habitId = ?', whereArgs: [id]); }); } static Future<void> updateHabit(Map<String, Object> oneHabit) async { final db = await database(); await db.update( 'habitTable', oneHabit, where: 'id = ?', whereArgs: [oneHabit['id']], ); } /// 批量插入笔记专用,性能比循环单条插入高10~100倍 static Future<void> batchInsertNotes(List<Map<String, Object>> noteList) async { final db = await database(); final batch = db.batch(); for (final note in noteList) { batch.insert('note', note, conflictAlgorithm: sqf.ConflictAlgorithm.replace); } await batch.commit(noResult: true, continueOnError: false); } /// 批量插入事件专用 static Future<void> batchInsertEvents(List<Map<String, Object>> eventList) async { final db = await database(); final batch = db.batch(); for (final event in eventList) { batch.insert('event', event, conflictAlgorithm: sqf.ConflictAlgorithm.replace); } await batch.commit(noResult: true, continueOnError: false); } }
额外性能优化注意项
- 超过100条数据的批量查询、批量写入操作,不要放在UI主线程执行,可将数据序列化/反序列化逻辑放到独立isolate中处理,避免阻塞UI渲染
- 列表类查询永远加分页参数,不要一次性加载全量数据,用户滑动到对应页码时再加载下一页数据
- 查询时只取需要的字段,不要无差别查所有字段,比如列表页只需要展示标题、时间就只查这两个字段,减少内存拷贝和序列化耗时
- 非必要不要开启
continueOnError,批量操作遇到错误直接中断回滚,避免脏数据堆积 - 若后续表结构变更,不要直接删库重建,写升级迁移逻辑,避免用户数据丢失
内容的提问来源于stack exchange,提问作者Legend_Programmar
相关产品推荐
相关产品推荐

