数据库与存储优化:SQLite性能调优、Room数据库优化、索引与事务、MMKV vs SharedPreferences
大家好,我是你们的老朋友。今天我们来聊聊Android开发中一个绕不开的话题——数据存储。说实话,我见过太多应用因为存储选型不当或者数据库用得太糙,导致卡顿、ANR,甚至数据丢失。我自己也踩过不少坑,今天就把这些经验掰开揉碎了讲给你听。
一、SQLite性能调优:别让数据库拖垮你的App
SQLite是Android内置的关系型数据库,轻量、可靠。但很多人用起来就是一把梭——建表、插入、查询,完事。结果呢?数据量一上来,页面就卡成PPT。
核心问题在哪?说白了,SQLite是单线程写入的。你想想看,如果多个线程同时写数据库,它就得排队。排队时间长了,UI线程一堵,ANR就来了。
1. 使用WAL模式
WAL(Write-Ahead Logging)模式允许读写并发。我习惯在数据库初始化时直接开启:
db.execSQL("PRAGMA journal_mode=WAL");
开启后,读操作不会被写操作阻塞。我在项目中遇到过,一个聊天App的消息列表,开启WAL后滑动流畅度提升了一个档次。
2. 批量操作要打包
别一条一条insert。用事务把多条操作包起来:
db.beginTransaction();
try {
for (Data data : list) {
db.insert("table", null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
这样做,磁盘I/O次数从N次降到了1次。性能差距,你试试就知道。
3. 避免不必要的索引
索引不是越多越好。写操作频繁的字段加索引,反而会拖慢插入速度。我建议只对WHERE、JOIN、ORDER BY中频繁使用的字段建索引。
二、Room数据库优化:Google推荐的ORM到底强在哪?
Room是Google官方推出的ORM框架,底层封装了SQLite。我个人非常推荐用它,因为它帮你规避了很多低级错误。
1. 避免在主线程操作数据库
Room默认不允许在主线程做数据库操作,除非你调用allowMainThreadQueries()。但千万别这么干!我见过有人为了省事,直接在主线程查数据库,结果列表加载时界面直接冻住。
正确做法是用LiveData或Flow配合协程:
@Query("SELECT * FROM user")
fun getAllUsers(): Flow<List<User>>
这样数据变化时自动通知UI,而且不会阻塞主线程。
2. 使用@Transaction注解
如果你在一个方法里操作多个表,记得加上@Transaction。Room会帮你处理好事务边界:
@Transaction
@Query("SELECT * FROM user WHERE id = :userId")
suspend fun getUserWithPosts(userId: Int): UserWithPosts
嗯,这里要注意:@Transaction只对@Query方法有效,对@Insert、@Update、@Delete是自动事务的。
3. 预填充数据库
如果你的App首次启动需要大量初始数据,别在运行时一条条插。用RoomDatabase.Callback配合createFromAsset(),直接把预置的数据库文件拷贝过来:
Room.databaseBuilder(context, AppDatabase.class, "mydb.db")
.createFromAsset("databases/prepopulated.db")
.build()
这样做,首次启动速度能快10倍以上。
三、索引与事务:数据库性能的左右手
索引和事务,是数据库优化的两个核心武器。用好了,性能翻倍;用不好,反而添乱。
1. 索引的选择
索引类型主要有三种:
| 索引类型 | 适用场景 | 注意事项 |
|---|---|---|
| 单列索引 | 单个字段频繁查询 | 字段区分度要高 |
| 复合索引 | 多字段联合查询 | 最左前缀原则 |
| 唯一索引 | 保证字段唯一性 | 插入时会有额外检查开销 |
我曾经在一个订单表上建了复合索引(user_id, status, create_time),查询速度从2秒降到了50毫秒。但要注意,复合索引的字段顺序很重要,最常查询的字段放最左边。
2. 事务的合理使用
事务不只是为了批量操作。它还能保证数据一致性。举个例子:转账操作,扣钱和加钱必须在一个事务里,否则中间崩溃了,钱就丢了。
但事务也不是越大越好。一个事务里包含太多操作,会长时间持有锁,阻塞其他线程。我建议单个事务控制在1000条以内。
四、MMKV vs SharedPreferences:谁才是K-V存储之王?
SharedPreferences(简称SP)是Android最古老的键值对存储方式。但它的性能问题一直被诟病。MMKV是腾讯开源的高性能K-V组件,底层用mmap实现。
1. 性能对比
| 特性 | SharedPreferences | MMKV |
|---|---|---|
| 写入方式 | 全量写入XML | 增量写入mmap |
| 读取速度 | 全量加载到内存 | 按需读取 |
| 多进程支持 | 不支持 | 支持 |
| 线程安全 | 是 | 是 |
| 初始化耗时 | 几十毫秒 | 几毫秒 |
我做过一个测试:写入1000个键值对,SP耗时约800ms,MMKV只要15ms。差距不是一星半点。
2. 什么时候用SP?什么时候用MMKV?
我个人建议:
- 小数据量(几十个键值对):SP够用,没必要引入MMKV的依赖。
- 频繁写入或大数据量:果断上MMKV。比如用户设置、缓存数据。
- 多进程场景:只能用MMKV。SP不支持多进程,强行用会数据错乱。
我曾经在一个项目中,用SP存储用户登录态,每次启动App都要读几十个键。换成MMKV后,启动速度提升了30%。
3. MMKV的使用示例
// 初始化
MMKV.initialize(this);
// 获取默认实例
MMKV kv = MMKV.defaultMMKV();
// 写入
kv.encode("user_name", "张三");
kv.encode("user_age", 25);
// 读取
String name = kv.decodeString("user_name", "");
int age = kv.decodeInt("user_age", 0);
代码非常简洁,而且支持多种数据类型。嗯,这里要注意:MMKV的key不要用中文,虽然能存,但跨平台兼容性不好。
五、知识体系总览
下面这张图,帮你理清今天讲的所有内容:
好了,今天的内容就到这里。数据存储优化不是一蹴而就的事,需要根据实际场景不断调整。记住一句话:没有银弹,只有最适合的方案。