本地数据库是 App 的标配。Room 让 SQLite 写起来像操作对象,但用久了会发现:同样的 Room,有人快有人慢,差别就在索引和查询设计。
一、为什么 Room 查询会慢
Room 本质还是 SQLite。慢通常不是 Room 的问题,是 SQL 的问题:
没建索引,全表扫描
一次性查太多列
主线程执行查询
N+1 查询
二、索引怎么加
@Entity(
tableName = "messages",
indices = [Index(value = ["conversation_id", "create_time"])]
)
data class MessageEntity(
@PrimaryKey val id: String,
val conversationId: String,
val content: String,
val createTime: Long
)
复合索引的顺序很重要。上面的索引对 (conversation_id, create_time) 查询高效,但对单独的 create_time 查询不生效。
可以用 @DatabaseView 或 EXPLAIN QUERY PLAN 分析查询计划。
三、数据库迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE messages ADD COLUMN priority INTEGER DEFAULT 0")
}
}
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.build()
迁移规则:
新增列用
ALTER TABLE ADD COLUMN新增表用
CREATE TABLE改表结构用创建临时表 → 导数据 → 删旧表 → 重命名
决不能删已有列的旧数据,除非业务允许
四、Room 结合 Flow
@Query("SELECT * FROM messages WHERE conversation_id = :id ORDER BY create_time DESC")
fun observeMessages(id: String): Flow<List<MessageEntity>>
数据库有变化时,Flow 会自动 emit 新数据。UI 层配合 collectAsStateWithLifecycle 用非常舒服。
五、性能优化 checklist
主键用整数:自增 INTEGER 比 String 更高效
分页查询:用
LIMIT/OFFSET或 Paging 3避免存大对象:图片、JSON 字符串建议放文件,数据库只存路径
批量插入用事务:
database.runInTransaction {
messageDao.insertAll(list)
}
查询放后台:Room 支持 suspend 函数和 Flow
小结
Room 慢多半是 SQL 没写好
索引顺序影响查询效率,要用 EXPLAIN 分析
迁移要显式写,不要靠
fallbackToDestructiveMigration()Room + Flow 是本地数据层的最佳搭档
下一篇,我们看看后台任务:WorkManager 怎么用一套 API 搞定定时、约束、重试。
参考阅读
引用链接
[1]Room 官方文档: https://developer.android.com/training/data-storage/room
[2]SQLite Query Planning: https://sqlite.org/queryplanner.html