在Flutter应用开发中,如果需要保存结构化数据,比如用户信息、聊天记录、商品列表,单靠SharedPreferences这类键值存储显然不够用。sqflite是Flutter社区最成熟的SQLite数据库插件,它在原生侧封装了SQLite引擎,通过MethodChannel与Dart层通信,所有数据库操作都在后台线程执行,不会阻塞UI。本文将从环境搭建开始,完整讲解sqflite的使用方法和进阶技巧。

一、安装配置与数据库初始化
sqflite的安装非常简单,在pubspec.yaml中添加依赖即可。它通常配合path插件一起使用,用于拼接数据库文件的存储路径。先执行flutter pub add命令安装:
dependencies: sqflite: ^2.3.0 path: ^1.8.3
安装完成后,还需要初始化数据库。sqflite推荐使用单例模式管理数据库实例,避免多次打开同一个数据库文件造成资源浪费。下面是典型的初始化代码:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
static Database? _database;
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), 'app.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE user(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
''');
},
);
}
}
这段代码有几个关键点值得注意。getDatabasesPath()返回的是应用沙盒目录,Android和iOS的路径各不相同,插件已经做了兼容处理,开发者不需要自己判断平台。onCreate回调只会在数据库第一次创建时执行,如果文件已存在则直接跳过,所以建表逻辑放心写在这里面即可。
另外,version参数非常重要,它和后面要讲的数据库升级机制直接相关。每次发布新版本修改了表结构,都要记得递增这个版本号,否则onUpgrade回调不会触发,这是新手最容易踩的坑之一。
二、增删改查操作的实现
数据库初始化好之后,就可以进行CRUD操作了。sqflite提供了两种方式:一种是直接写SQL语句的rawInsert、rawQuery系列方法,另一种是封装好的便捷方法insert、query、update、delete。两者性能差别不大,便捷方法能自动处理参数转义,建议优先使用。
插入数据时,传入一个Map,键对应表字段名,值就是要存的数据。查询则支持分页、排序、条件过滤,写法如下:
// 插入一条用户记录
Future<int> insertUser(Map<String, dynamic> user) async {
final db = await DatabaseHelper().database;
return await db.insert('user', user);
}
// 查询年龄大于18的用户,按年龄降序,取前20条
Future<List<Map<String, dynamic>>> getAdultUsers() async {
final db = await DatabaseHelper().database;
return await db.query(
'user',
where: 'age > ?',
whereArgs: [18],
orderBy: 'age DESC',
limit: 20,
offset: 0,
);
}
// 更新数据
Future<int> updateUser(int id, String newName) async {
final db = await DatabaseHelper().database;
return await db.update(
'user',
{'name': newName},
where: 'id = ?',
whereArgs: [id],
);
}
// 删除数据
Future<int> deleteUser(int id) async {
final db = await DatabaseHelper().database;
return await db.delete('user', where: 'id = ?', whereArgs: [id]);
}
这里强烈建议使用?占位符配合whereArgs传参,而不是直接拼接SQL字符串。占位符方式可以天然防止SQL注入攻击,而且sqflite内部会自动处理字符串转义。如果非要拼接,用户输入中一旦包含单引号就会导致SQL语法错误,甚至被恶意注入。
查询返回的是List<Map<String, dynamic>>,实际项目中一般会定义一个模型类,配合fromJson和toJson方法做转换,这样上层代码不直接接触Map,可维护性会好很多。
三、事务与批量操作的性能优化
当需要一次性写入大量数据时,比如批量导入一千条商品记录,逐条调用insert的效率会非常低。因为每次insert都会开启一个隐式事务并刷盘,一千条数据就意味着一千次磁盘IO。正确的做法是使用事务或者batch。
事务的使用方式是在transaction回调中执行所有操作,要么全部成功,要么全部回滚:
Future<void> batchInsertUsers(List<Map<String, dynamic>> users) async {
final db = await DatabaseHelper().database;
await db.transaction((txn) async {
for (final user in users) {
await txn.insert('user', user);
}
});
}
// 更高性能的batch方式
Future<void> batchInsert(List<Map<String, dynamic>> users) async {
final db = await DatabaseHelper().database;
final batch = db.batch();
for (final user in users) {
batch.insert('user', user);
}
await batch.commit(noResult: true);
}
batch方式和事务的区别在于,batch把所有操作打包后一次性发送到原生侧执行,减少了Dart与原生之间的通信次数,而且设置noResult: true后不返回每条操作的结果,性能还能进一步提升。实测插入一万条数据,逐条插入可能需要十几秒,而batch方式通常不到一秒,差距非常明显。
需要注意,在transaction回调内部必须使用传入的txn对象操作数据库,而不能使用外部的db实例,否则会抛出deadlock异常,因为数据库已经被当前事务锁定了。
四、数据库版本升级与常见坑点
应用迭代过程中表结构难免变化,sqflite通过onUpgrade回调和版本号机制处理迁移。当打开数据库时传入的version大于已存在的版本号,就会触发onUpgrade:
return await openDatabase(
path,
version: 2,
onCreate: (db, version) async {
await db.execute('CREATE TABLE user(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)');
},
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE user ADD COLUMN email TEXT');
}
},
);
升级逻辑要用if (oldVersion < n)的形式逐级判断,而不是直接判断等于某个版本。因为用户可能从版本1直接升到版本4,中间所有的迁移步骤都要依次执行,漏掉任何一步都会导致表结构和代码不匹配。
最后补充几个常见问题。第一,sqflite在iOS模拟器上运行正常但某些老设备上可能报找不到SQLite库,一般升级Xcode即可解决。第二,如果需要在桌面端Windows或Linux使用,需要改用sqflite_common_ffi包,它提供了基于FFI的纯Dart实现。第三,调试时可以用db.getVersion()和databaseFactory.deleteDatabase(path)来检查和重置数据库状态,避免缓存的旧库文件干扰测试结果。
总的来说,sqflite的API设计简洁清晰,配合单例封装和模型类,可以搭建出相当可靠的本地数据层。掌握事务、batch和版本迁移这三个核心点,基本就能应对绝大多数业务场景了。
Flutter sqfliteSQLite数据库Flutter本地存储修改时间:2026-09-05 19:10:52