Day3_开源鸿蒙_Flutter_for_OpenHarmony_sqflite避坑_锁库与迁移
开源鸿蒙 Flutter应用使用sqflite数据库避坑指南 文章针对开源鸿蒙Flutter应用中使用sqflite数据库时常见的锁库和迁移问题提供了解决方案。主要内容包括: 锁库问题分析:SQLite并发写入导致数据库锁定的常见场景和报错表现 解决方案: 采用写入队列机制实现串行化写入 通过AppDatabase.write()方法封装所有写操作 示例代码展示如何实现写入队列和DAO层改造 数据
开源鸿蒙 Flutter for OpenHarmony:sqflite避坑(锁库+迁移)
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
Day1/Day2 把离线笔记做到了“能用”,但一旦开始上强度(自动保存、快速删除、频繁切页),sqflite 最容易暴露两个问题:
- 锁库/并发写:典型表现是
DatabaseException(database is locked)、偶发保存失败、列表刷新不及时 - 升级迁移:表结构一改(加字段/加索引),旧数据如何平滑升级、升级后如何验证
这篇只讲 sqflite:怎么把它用稳、怎么把坑提前堵住。
1. sqflite 锁库是怎么来的(常见触发场景)
sqflite 底层是 SQLite。SQLite 同一时刻允许多个读,但写会有锁竞争;当应用层同时发起多次写入时,就有概率在某些机型/某些时序下撞上锁。
离线笔记最容易触发并发写的场景:
- 编辑页自动保存(防抖) + 手动点击保存按钮同时触发写入
- 返回页面兜底保存,和上一轮自动保存写入“时间撞车”
- 列表页左滑删除触发写入,恰好编辑页也在写入
典型报错(示意):
DatabaseException(database is locked)
2. 解决思路:把所有写操作串行化(同一时刻只写一次)
很多人第一反应是“加大超时/重试”,但更稳的做法是:应用层写入队列。
目标很简单:所有 insert/update/delete 统一排队执行,保证同一时刻只有一个写事务在跑。
这一步直接围绕 sqflite 的写接口做封装,不需要引入新库。
3. 实现写入队列:AppDatabase.write(…)
📌 文件:lib/features/note/data/app_database.dart
下面这个 write 方法就是“写入队列”的核心:
class AppDatabase {
AppDatabase._();
static final AppDatabase instance = AppDatabase._();
Database? _db;
Future<void> _writeChain = Future.value();
Future<Database> get database async {
final existing = _db;
if (existing != null) return existing;
final db = await _open();
_db = db;
return db;
}
Future<T> write<T>(Future<T> Function(Database db) action) {
final future = _writeChain.then((_) async {
final db = await database;
return action(db);
});
_writeChain = future.then((_) {}, onError: (_) {});
return future;
}
}
这段代码做了什么:
_writeChain是一条 Future 链- 每次写入都挂在链尾部,自动变成串行执行
- 写失败不会把队列卡死(用
onError把链继续往后走)
📌 这一步解决的核心问题:
- 多处业务同时触发写入时,不会并发抢锁,而是排队一个个写
4. DAO 写操作统一走 write 队列(insert/update/delete)
📌 文件:lib/features/note/data/note_dao.dart
把写操作从 await _db.database 改成 _db.write(...):
Future<int> insert(Note note) async {
return _db.write((db) => db.insert('notes', _toInsertRow(note)));
}
Future<int> update(Note note) async {
final id = note.id;
if (id == null) throw StateError('Cannot update note without id');
return _db.write(
(db) => db.update(
'notes',
_toUpdateRow(note),
where: 'id = ?',
whereArgs: [id],
),
);
}
Future<int> softDelete(int id) async {
return _db.write(
(db) => db.update(
'notes',
{
'is_deleted': 1,
'updated_at': DateTime.now().millisecondsSinceEpoch,
},
where: 'id = ?',
whereArgs: [id],
),
);
}
这段改动的意义:
- 所有写入入口被收口到
AppDatabase.write - UI/Repository 不需要关心“锁库/并发”细节
- 后续要做事务(transaction)也有一个统一位置可加
5. 迁移怎么做:升级 DB version + onUpgrade
离线应用很难避免“表结构变化”。最常见的变化是加字段、加索引。
本例演示一个最安全的迁移:给 notes 表加一个 pinned 字段(置顶),默认 0。
📌 文件:lib/features/note/data/app_database.dart
return openDatabase(
path,
version: 2,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE notes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
pinned INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
''');
},
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute(
"ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0",
);
}
},
);
迁移写法的要点:
version必须递增(否则onUpgrade不会触发)onUpgrade按oldVersion分段处理,保证可以从任意旧版本升级到新版本ALTER TABLE ... ADD COLUMN是 SQLite 比较稳妥的迁移方式之一(不会动旧数据)
6. 迁移后,模型与映射也要跟着改(否则读写会对不上)
📌 文件:lib/features/note/data/note.dart
class Note {
final bool pinned;
// ...
}
📌 文件:lib/features/note/data/note_dao.dart
Map<String, Object?> _toInsertRow(Note note) => {
'pinned': note.pinned ? 1 : 0,
// ...
};
Note _fromRow(Map<String, Object?> row) => Note(
pinned: (row['pinned'] as int? ?? 0) != 0,
// ...
);
这一步的意义:
- 建表/迁移只是“数据库层面能存”,模型映射不改会导致“代码层面读写对不上”
7. 自测清单(Day3)
🧪 锁库/并发写:
- 编辑页快速输入 + 立刻点保存 + 立刻返回,确认不会出现保存失败
- 列表页快速连续删除,确认不会卡死
🧪 迁移:
- 已有旧数据的情况下升级到新版本,确认旧笔记仍存在
- 新建/编辑/删除流程正常
📷 
---
## 8. 下一步(Day4 方向)
Day4 可以开始做“私密笔记”,核心第三方库:`flutter_secure_storage`(敏感字段不进明文数据库)。
更多推荐



所有评论(0)