Administrator
发布于 2026-08-25 / 30 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 20 篇:Room 数据库进阶:索引、迁移与性能优化

本地数据库是 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

  1. 主键用整数:自增 INTEGER 比 String 更高效

  2. 分页查询:用 LIMIT/OFFSET 或 Paging 3

  3. 避免存大对象:图片、JSON 字符串建议放文件,数据库只存路径

  4. 批量插入用事务:

database.runInTransaction {
    messageDao.insertAll(list)
}
  1. 查询放后台:Room 支持 suspend 函数和 Flow


小结

  1. Room 慢多半是 SQL 没写好

  2. 索引顺序影响查询效率,要用 EXPLAIN 分析

  3. 迁移要显式写,不要靠 fallbackToDestructiveMigration()

  4. Room + Flow 是本地数据层的最佳搭档

下一篇,我们看看后台任务:WorkManager 怎么用一套 API 搞定定时、约束、重试。


参考阅读

引用链接

[1]Room 官方文档: https://developer.android.com/training/data-storage/room

[2]SQLite Query Planning: https://sqlite.org/queryplanner.html


评论