29、性能监控:慢查询检测、数据库大小监控、ANR预防、Profiler使用

说实话,Room 用起来确实爽,写个 DAO 接口、定义个 Entity,CRUD 就全有了。但爽归爽,性能问题该来还是会来。我见过不少项目,上线后用户反馈「卡死了」「点一下等半天」,最后定位到是数据库慢查询导致的。

所以这一章,咱们就聊聊怎么给 Room 做「体检」。说白了,就是四个维度:慢查询检测、数据库大小监控、ANR 预防、Profiler 使用。掌握了这些,你才算真正会用 Room。

核心观点:性能监控不是上线后才做的事,而是开发阶段就要养成的习惯。

29.1 慢查询检测:别让 SQL 拖垮你的 UI

慢查询,就是执行时间超过预期的 SQL 语句。在 Room 里,默认的 SQLite 执行速度其实不慢,但一旦数据量上来,或者查询写得不好,问题就暴露了。

我自己的习惯是,在开发阶段就开启 Room 的查询日志。怎么开?很简单:

// 在构建 Room 数据库时,开启查询日志
val db = Room.databaseBuilder(
    context,
    AppDatabase::class.java,
    "my_database.db"
)
.allowMainThreadQueries() // 仅用于调试,别上线
.setQueryCallback(object : RoomDatabase.QueryCallback {
    override fun onQuery(sql: String, bindArgs: List<Any?>) {
        Log.d("RoomMonitor", "SQL: $sql, Args: $bindArgs")
    }
})
.build()

这样每条 SQL 都会打印出来。但光打印还不够,你得知道哪条慢。我建议配合 SlowQueryDetector 来用:

class SlowQueryDetector(private val thresholdMs: Long = 200) : RoomDatabase.QueryCallback {
    private val queryTimes = mutableMapOf<String, Long>()

    override fun onQuery(sql: String, bindArgs: List<Any?>) {
        val start = System.currentTimeMillis()
        // 这里可以结合 AOP 或自定义 DAO 来记录结束时间
        // 简单做法:在 DAO 方法前后手动计时
    }

    fun reportSlowQuery(sql: String, durationMs: Long) {
        if (durationMs > thresholdMs) {
            Log.w("SlowQuery", "慢查询: $sql, 耗时: ${durationMs}ms")
            // 可以上报到后台
        }
    }
}
我的经验:阈值设多少合适?我个人习惯设 200ms。超过 200ms 的查询,在 UI 线程上就会让用户感觉到「顿一下」。如果查询超过 500ms,那基本就是 ANR 的种子选手了。

29.2 数据库大小监控:别让 DB 变成「胖子」

数据库文件太大,也会拖慢性能。你想想看,一个 100MB 的数据库,打开都要好几秒。我遇到过最夸张的一次,有个社交 App 的数据库涨到了 800MB,用户手机直接卡死。

监控数据库大小,其实很简单。Room 的数据库文件存在 context.getDatabasePath("xxx.db") 里。你可以定期检查:

fun getDatabaseSize(context: Context, dbName: String): Long {
    val dbFile = context.getDatabasePath(dbName)
    return if (dbFile.exists()) dbFile.length() else 0L
}

// 使用示例
val size = getDatabaseSize(context, "my_database.db")
Log.d("DBMonitor", "数据库大小: ${size / 1024 / 1024} MB")

我建议在 App 启动时、或者每次数据库写入操作后,都检查一下大小。如果发现异常增长,就要排查是不是有缓存表没清理,或者日志表无限膨胀。

注意:数据库文件大小不等于实际数据量。SQLite 有 WAL 模式和碎片问题,有时候文件很大但实际数据不多。这时候可以执行 VACUUM 来压缩。

29.3 ANR 预防:别让 Room 成为「罪魁祸首」

ANR(Application Not Responding)是 Android 开发者的噩梦。而 Room 导致 ANR 的常见原因就一个:在主线程执行了耗时数据库操作

Room 默认会检查你是否在主线程执行数据库操作,如果检测到,会直接抛异常。但有些人为了省事,调用了 allowMainThreadQueries(),这就埋下了隐患。

我自己的原则是:所有数据库操作都走协程。Room 从 2.1 版本开始就原生支持协程,用起来非常丝滑:

// DAO 中定义挂起函数
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Long): User?
}

// ViewModel 中调用
class UserViewModel(private val userDao: UserDao) : ViewModel() {
    fun loadUser(userId: Long) {
        viewModelScope.launch {
            val user = withContext(Dispatchers.IO) {
                userDao.getUserById(userId)
            }
            // 更新 UI
        }
    }
}

但光用协程还不够。有时候你明明在 IO 线程执行,但查询太慢,导致协程挂起时间过长,用户操作还是卡。这时候就需要结合慢查询检测来优化。

避坑指南:我曾经在一个项目中,用 Flow 监听数据库变化,结果在 UI 线程上做了大量数据转换,导致 ANR。记住:Flow 的收集操作默认在协程上下文中执行,如果你在 collect 里做耗时操作,记得切线程。

29.4 Profiler 使用:用工具说话

手动打日志虽然有用,但真正定位性能瓶颈,还得靠工具。Android Studio 自带的 Profiler 就是最好的帮手。

怎么用?我一般分三步:

  1. 打开 Profiler:在 Android Studio 底部工具栏点击「Profiler」,或者用快捷键 Ctrl+Shift+A 搜索。
  2. 选择数据库监控:在 Profiler 面板里,选择「Database」标签。这里会显示所有数据库操作的时间线。
  3. 分析慢查询:点击某个查询,可以看到详细的 SQL 语句、执行时间、绑定参数。如果发现某个查询执行时间特别长,那就是优化目标。

Profiler 还能看到数据库连接数、事务数等指标。我习惯在测试场景下跑一遍核心流程,然后看 Profiler 的数据库面板,找出那些「异常尖峰」。

小技巧:Profiler 的数据库监控默认只显示最近的操作。如果你想保存数据做对比,可以点击「Export」按钮导出为 CSV 文件。这样就能对比不同版本之间的性能变化。

29.5 知识体系总览

下面这张图,把本章的核心内容串起来了。你可以把它当作一个检查清单:

Room 性能监控知识体系 慢查询检测 • 开启 QueryCallback 日志 • 设置阈值(建议 200ms) • 上报慢查询到后台 • 结合 AOP 自动计时 数据库大小监控 • 定期检查 db 文件大小 • 发现异常增长及时告警 • 执行 VACUUM 压缩碎片 • 清理无用缓存表 ANR 预防 • 禁止主线程数据库操作 • 使用协程 + Dispatchers.IO • Flow collect 中注意线程切换 • 结合慢查询检测提前发现 Profiler 使用 • 打开 Database 监控面板 • 分析 SQL 执行时间线 • 导出 CSV 做对比分析 • 关注连接数和事务数 目标:让 Room 查询又快又稳,用户无感知

29.6 实战建议:建立监控闭环

光有工具还不够,你得形成一套闭环流程。我自己的做法是:

  • 开发阶段:开启慢查询日志 + Profiler 实时监控,发现慢查询立即优化。
  • 测试阶段:用自动化脚本跑核心场景,收集数据库大小和查询耗时数据。
  • 上线后:通过埋点上报慢查询和数据库大小,设置告警阈值。

举个例子,我曾在线上环境发现某个查询平均耗时 800ms,排查后发现是 LIKE '%keyword%' 导致的。改成全文索引后,耗时降到 50ms。如果没有监控,这个问题可能永远发现不了。

最后提醒一句:性能监控不是一次性工作。随着业务迭代,数据量增长,以前不慢的查询可能变慢。所以,把监控当成日常习惯,而不是临时抱佛脚。

公众号:蓝海资料掘金营,微信deep3321