Skip to content

Android 数据库优化

目录

  1. 引言
  2. [Room 查询优化](#room 查询优化)
  3. 索引优化
  4. 事务管理
  5. 避免主线程查询
  6. 分页加载
  7. 批量操作
  8. 数据库迁移优化
  9. 性能监控
  10. 面试考点
  11. 最佳实践
  12. 总结

引言

数据库性能直接影响应用的用户体验。慢查询、数据库锁、主线程查询等问题都会导致应用卡顿甚至崩溃。本文将深入探讨 Android 数据库优化的各个方面,提供实用的优化方案和代码示例。

数据库性能指标

指标说明目标值
查询响应时间查询完成所需时间< 50ms
写入响应时间写入完成所需时间< 100ms
数据库大小数据库文件体积< 100MB
并发查询数同时进行的查询数< 5

Room 架构

┌────────────────────────────────┐
│          Room 架构             │
├────────────────────────────────┤
│  Entity  →  数据表结构定义      │
│  DAO    →  数据访问对象        │
│  Database → 数据库管理类       │
│  TypeConverter → 类型转换      │
└────────────────────────────────┘

Room 查询优化

基础查询优化

kotlin
// ❌ 错误:低效的查询
@Query("SELECT * FROM users")
fun getAllUsers(): List<User>

// ✅ 正确:只查询需要的字段
@Query("SELECT id, name, email FROM users")
fun getUserBasicInfo(): List<UserBasic>

data class UserBasic(
    val id: Long,
    val name: String,
    val email: String
)

查询条件优化

kotlin
// ✅ 使用索引列查询
@Query("SELECT * FROM users WHERE email = :email")
fun getUserByEmail(email: String): User?

// ✅ 使用 IN 查询代替多个 OR
@Query("SELECT * FROM users WHERE id IN (:ids)")
fun getUsersByIds(ids: List<Long>): List<User>

// ✅ 使用 LIMIT 限制结果
@Query("SELECT * FROM users ORDER BY createdAt DESC LIMIT :limit")
fun getRecentUsers(limit: Int = 20): List<User>

复杂查询优化

kotlin
// ❌ 错误:低效的关联查询
@Transaction
fun getUsersWithOrders(userId: Long): UserWithOrders {
    val user = userDao.getUserById(userId)
    val orders = orderDao.getOrdersByUserId(userId)
    return UserWithOrders(user, orders)
}

// ✅ 正确:使用单个查询
@Query("""
    SELECT u.*, o.id as orderId, o.total as orderTotal
    FROM users u
    LEFT JOIN orders o ON u.id = o.userId
    WHERE u.id = :userId
""")
fun getUserWithOrders(userId: Long): UserWithOrders?

Flow 查询优化

kotlin
// ✅ 使用 Flow 实时监听
@Query("SELECT * FROM users WHERE isActive = 1")
fun getActiveUsersFlow(): Flow<List<User>>

// ✅ 使用 map 转换数据
val userFlow = dao.getActiveUsersFlow()
    .map { users ->
        users.map { it.name }
    }
    .flowOn(IO_DISPATCHER)
    .collect { names ->
        updateUI(names)
    }

// ✅ 使用 distinctUntilChanged
val optimizedFlow = dao.getActiveUsersFlow()
    .distinctUntilChanged()
    .flowOn(IO_DISPATCHER)
    .collect {}

索引优化

索引创建

kotlin
// 创建索引
@Entity(
    tableName = "users",
    indexes = [
        Index(value = ["email"], unique = true),
        Index(value = ["createdAt"]),
        Index(value = ["status", "createdAt"]) // 复合索引
    ]
)
data class User(
    @PrimaryKey val id: Long,
    val email: String,
    val createdAt: Long,
    val status: String
)

索引优化策略

kotlin
class IndexOptimizer {
    // 1. 为查询条件列创建索引
    @Query("SELECT * FROM users WHERE email = :email")
    fun getUserByEmail(email: String): User? // email 需要索引
    
    // 2. 为排序列创建索引
    @Query("SELECT * FROM users ORDER BY createdAt DESC")
    fun getUsersByDate(): List<User> // createdAt 需要索引
    
    // 3. 为关联查询列创建索引
    @Query("SELECT * FROM orders WHERE userId = :userId")
    fun getOrdersByUser(userId: Long): List<Order> // userId 需要索引
}

复合索引

kotlin
// 复合索引示例
@Entity(
    tableName = "orders",
    indexes = [
        // 覆盖查询条件
        Index(value = ["userId", "status", "createdAt"])
    ]
)
data class Order(
    @PrimaryKey val id: Long,
    val userId: Long,
    val status: String,
    val createdAt: Long
)

// 复合索引可以覆盖以下查询
@Query("SELECT * FROM orders WHERE userId = :userId AND status = :status ORDER BY createdAt DESC")
fun getOrdersByUserAndStatus(userId: Long, status: String): List<Order>

索引分析

kotlin
class IndexAnalyzer {
    // 分析查询是否使用索引
    fun analyzeQuery(query: String): QueryAnalysis {
        // 执行 EXPLAIN
        val db = database.writableDatabase
        val explainResult = db.rawQuery("EXPLAIN $query", null)
        
        return QueryAnalysis(
            usesIndex = explainResult?.let { it.useIndex },
            scanType = explainResult?.let { it.scanType },
            estimatedRows = explainResult?.estimatedRows ?: 0
        )
    }
}

事务管理

基础事务

kotlin
// ❌ 错误:未使用事务
fun insertUsers(users: List<User>) {
    users.forEach { user ->
        dao.insertUser(user) // 每次都提交事务
    }
}

// ✅ 正确:使用事务
@Transaction
fun insertUsers(users: List<User>) {
    users.forEach { user ->
        dao.insertUser(user)
    }
}

事务回滚

kotlin
class TransactionManager {
    @Transaction
    fun transferMoney(fromId: Long, toId: Long, amount: Double): Result<Boolean> {
        return try {
            val fromUser = dao.getUserById(fromId)
            val toUser = dao.getUserById(toId)
            
            if (fromUser?.balance ?: 0.0 < amount) {
                return Result.failure("余额不足")
            }
            
            dao.updateBalance(fromId, fromUser.balance - amount)
            dao.updateBalance(toId, toUser.balance + amount)
            
            Result.success(true)
        } catch (e: Exception) {
            // 自动回滚
            Result.failure(e.message)
        }
    }
}

嵌套事务

kotlin
class NestedTransaction {
    @Transaction
    fun complexTransaction() {
        // 外层事务
        dao.insertUser(user)
        
        runTransaction {
            // 内层事务
            dao.insertOrder(order)
            dao.insertOrderItem(items)
        }
    }
    
    private fun runTransaction(block: () -> Unit) {
        val db = AppDatabase.getInstance().writableDatabase
        db.runWithTransaction {
            block()
        }
    }
}

事务性能优化

kotlin
class OptimizedTransaction {
    // 批量插入事务
    @Transaction
    fun batchInsertUsers(users: List<User>): Int {
        return dao.insertUsers(users).size
    }
    
    // 避免在事务中执行耗时操作
    @Transaction
    fun insertWithValidation(users: List<User>) {
        // 验证在事务外
        val validUsers = users.filter { isUserValid(it) }
        
        // 事务中只执行数据库操作
        dao.insertUsers(validUsers)
    }
    
    private fun isUserValid(user: User): Boolean {
        // 验证逻辑
        return true
    }
}

避免主线程查询

使用 suspend 函数

kotlin
// ✅ 使用 suspend 函数
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getAllUsers(): List<User>
    
    @Insert
    suspend fun insertUser(user: User): Long
}

// 在 ViewModel 中使用
class UserViewModel : ViewModel() {
    fun getUsers() {
        lifecycleScope.launch {
            val users = repository.getAllUsers()
            // 更新 UI
        }
    }
}

使用 LiveData/Flow

kotlin
// ✅ 使用 LiveData
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsersLiveData(): LiveData<List<User>>
}

// ✅ 使用 Flow
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsersFlow(): Flow<List<User>>
}

// ViewModel 使用
class UserViewModel(private val dao: UserDao) : ViewModel() {
    val users: LiveData<List<User>> = dao.getAllUsersLiveData()
    
    val usersFlow: Flow<List<User>> = dao.getAllUsersFlow()
        .flowOn(Dispatchers.IO)
}

异步查询

kotlin
// 使用协程进行异步查询
class UserRepository(private val dao: UserDao) {
    suspend fun getUsersWithDelay(): List<User> {
        return withContext(Dispatchers.IO) {
            dao.getAllUsers()
        }
    }
}

// 避免在主线程执行
class MainThreadSafety {
    // ❌ 错误:主线程查询
    fun badExample() {
        val users = dao.getAllUsers() // 可能阻塞主线程
    }
    
    // ✅ 正确:异步查询
    suspend fun goodExample() {
        val users = withContext(Dispatchers.IO) {
            dao.getAllUsers()
        }
    }
}

分页加载

Paging 3 使用

kotlin
// DAO 配置
@Dao
interface UserDao {
    @Query("SELECT * FROM users ORDER BY createdAt DESC")
    fun getPagingData(): PagerConfig
    
    data class PagerConfig(
        val id: Long,
        val name: String,
        val createdAt: Long
    )
}

// Repository 配置
class UserRepository(private val dao: UserDao) {
    fun getPagedUsers(): Pager<Int, User> {
        return Pager(
            config = PagingConfig(
                pageSize = 20,
                prefetchDistance = 10,
                enablePlaceholders = false
            )
        ) {
            dao.getPagingData()
        }
    }
}

分页优化

kotlin
// 分页加载优化
class PagingOptimizer {
    // 1. 设置合理的 pageSize
    val pagingConfig = PagingConfig(
        pageSize = 20,          // 每页数量
        prefetchDistance = 10,  // 预加载距离
        initialLoadSize = 40    // 初始加载量
    )
    
    // 2. 使用 Placeholder 避免闪烁
    val configWithPlaceholders = PagingConfig(
        enablePlaceholders = true
    )
    
    // 3. 添加刷新和重试
    val refreshablePaging = Pager(config) {
        dao.getPagingData()
    }.flow.cachedIn(viewModelScope)
}

虚拟滚动

kotlin
// RecyclerView 分页适配器
class PagingAdapter : PagingDataAdapter<User, UserViewHolder>(DIFF_CALLBACK) {
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        user?.let {
            holder.bind(it)
        }
    }
}

// 使用 DiffUtil
companion object {
    private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }
        
        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
}

批量操作

批量插入

kotlin
// ✅ 批量插入
@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(users: List<User>): List<Long>
    
    @Insert
    suspend fun insert(user: User): Long
}

// 使用
repository.insertAll(users) // 一次性插入所有用户

批量更新

kotlin
// 批量更新
@Dao
interface UserDao {
    @Update
    suspend fun updateAll(users: List<User>)
    
    @Update
    suspend fun update(user: User)
}

// 批量更新优化
class BatchUpdateOptimizer {
    @Transaction
    suspend fun updateUsersBatch(users: List<User>) {
        // 批量更新
        dao.updateAll(users)
    }
}

批量删除

kotlin
// 批量删除
@Dao
interface UserDao {
    @Delete
    suspend fun deleteAll(users: List<User>)
    
    @Query("DELETE FROM users WHERE id IN (:ids)")
    suspend fun deleteByIds(ids: List<Long>)
}

// 批量删除优化
class BatchDeleteOptimizer {
    @Transaction
    suspend fun deleteUsersBatch(ids: List<Long>) {
        dao.deleteByIds(ids)
    }
}

数据库迁移优化

迁移策略

kotlin
// 定义迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        // 添加新列
        database.execSQL("ALTER TABLE users ADD COLUMN age INTEGER")
        
        // 创建新表
        database.execSQL("""
            CREATE TABLE IF NOT EXISTS orders (
                id INTEGER PRIMARY KEY,
                userId INTEGER,
                total REAL
            )
        """)
    }
}

// 数据库配置
@Database(entities = [User::class, Order::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    
    companion object {
        @Volatile private var INSTANCE: AppDatabase? = null
        
        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                )
                .addMigrations(MIGRATION_1_2)
                .build()
                .also { INSTANCE = it }
            }
        }
    }
}

迁移优化

kotlin
// 大表迁移优化
class LargeTableMigration {
    val MIGRATION_2_3 = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            // 1. 创建新表
            database.execSQL("CREATE TABLE users_new (...)")
            
            // 2. 分批迁移数据
            migrateInBatches(database)
            
            // 3. 删除旧表
            database.execSQL("DROP TABLE users")
            
            // 4. 重命名新表
            database.execSQL("ALTER TABLE users_new RENAME TO users")
        }
        
        private fun migrateInBatches(database: SupportSQLiteDatabase) {
            val batchSize = 1000
            var offset = 0
            
            while (true) {
                val rows = database.query("""
                    SELECT * FROM users LIMIT $batchSize OFFSET $offset
                """)
                
                if (rows.count == 0) break
                
                // 插入到新表
                rows.forEach { /* insert to users_new */ }
                
                offset += batchSize
                rows.close()
            }
        }
    }
}

无迁移策略

kotlin
// 开发环境使用 CREATE_DROP
fun getDevDatabase(context: Context): AppDatabase {
    return Room.databaseBuilder(
        context,
        AppDatabase::class.java,
        "dev_database"
    )
    .fallbackToDestructiveMigration() // 版本变化时删除重建
    .build()
}

// 生产环境使用迁移
fun getProdDatabase(context: Context): AppDatabase {
    return Room.databaseBuilder(
        context,
        AppDatabase::class.java,
        "prod_database"
    )
    .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
    .build()
}

性能监控

查询性能监控

kotlin
class QueryPerformanceMonitor {
    private val queryTimings = ConcurrentHashMap<String, MutableList<Long>>()
    
    fun recordQuery(query: String, duration: Long) {
        queryTimings.computeIfAbsent(query) { mutableListOf() }
            .add(duration)
    }
    
    fun getAverageQueryTime(query: String): Long {
        return queryTimings[query]?.average()?.toLong() ?: 0
    }
    
    fun getSlowQueries(threshold: Long = 100): List<String> {
        return queryTimings.filterValues { 
            it.average() > threshold 
        }.keys.toList()
    }
}

数据库操作日志

kotlin
// Room 日志监听
class DatabaseLogger {
    fun enableLogging() {
        Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        )
        .addCallback(object : RoomDatabase.Callback() {
            override fun onOpen(database: SupportSQLiteDatabase) {
                // 开启 SQL 日志
                database.enableWriterDebugLogging = true
            }
        })
        .build()
    }
}

// 自定义查询日志
class QueryLoggerInterceptor {
    fun intercept(query: String, startTime: Long) {
        val duration = System.currentTimeMillis() - startTime
        
        if (duration > 100) {
            Log.w("DB", "Slow query: $query took $duration ms")
        }
    }
}

性能分析工具

kotlin
// 使用 Systrace 分析
class SystraceAnalyzer {
    fun startTrace() {
        Trace.beginSection("DatabaseOperation")
    }
    
    fun endTrace() {
        Trace.endSection()
    }
    
    fun traceQuery(dao: UserDao, userId: Long) {
        Trace.beginSection("getUserById")
        try {
            dao.getUserById(userId)
        } finally {
            Trace.endSection()
        }
    }
}

面试考点

基础考点

1. Room 原理

问题: Room 是如何工作的?

回答:

  • 编译时生成 DAO 实现
  • 使用 SQLite 作为底层
  • 提供类型安全的查询
  • 支持主线程查询检测

2. 索引优化

问题: 如何优化数据库查询?

回答:

  • 为查询条件列添加索引
  • 使用复合索引
  • 避免 SELECT *
  • 使用 LIMIT 限制结果

3. 事务管理

问题: 为什么要使用事务?

回答:

  • 保证数据一致性
  • 批量操作性能更好
  • 支持回滚
  • 减少 I/O 次数

进阶考点

1. 分页加载

问题: Paging 的工作原理?

回答:

  • 分批次加载数据
  • 预加载下一页
  • 使用 DiffUtil 优化
  • 支持刷新和重试

2. 数据库迁移

问题: 如何实现数据库迁移?

回答:

  • 使用 Migration 类
  • 分批迁移大表
  • 使用 ALTER TABLE
  • 考虑数据备份

高级考点

1. 并发控制

问题: 如何处理数据库并发?

回答:

  • 使用事务隔离
  • 避免长事务
  • 使用读写锁
  • 实现重试机制

2. 性能优化

问题: 如何优化数据库性能?

回答:

  • 添加合适的索引
  • 使用批量操作
  • 避免主线程查询
  • 使用分页加载

最佳实践

数据库配置最佳实践

kotlin
class DatabaseBestPractices {
    // 1. 使用单例模式
    object Database {
        val INSTANCE: AppDatabase by lazy {
            Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "app_database"
            )
            .addMigrations(MIGRATIONS)
            .build()
        }
    }
    
    // 2. 设置合理的缓存
    val databaseBuilder = Room.databaseBuilder(
        context,
        AppDatabase::class.java,
        "app_database"
    )
    .setQueryCacheSize(100) // 查询缓存大小
    
    // 3. 使用主线程查询检测
    .allowMainThreadQueries() // 仅开发环境
}

常见错误

1. 主线程查询

kotlin
// ❌ 错误:主线程查询
fun badExample() {
    val user = dao.getUserById(1) // 可能导致 ANR
}

// ✅ 正确:异步查询
suspend fun goodExample() {
    val user = withContext(Dispatchers.IO) {
        dao.getUserById(1)
    }
}

2. 未使用事务

kotlin
// ❌ 错误:未使用事务
fun badInsert(users: List<User>) {
    users.forEach { dao.insert(it) } // 多次提交
}

// ✅ 正确:使用事务
@Transaction
fun goodInsert(users: List<User>) {
    dao.insertAll(users) // 一次提交
}

总结

数据库优化是 Android 性能优化的关键环节。通过 Room 查询优化、索引优化、事务管理、避免主线程查询、分页加载、批量操作、数据库迁移优化和性能监控,可以显著提升数据库性能。

关键要点

  1. Room 查询:只查询需要的字段
  2. 索引优化:为查询条件列添加索引
  3. 事务管理:使用事务批量操作
  4. 避免主线程:使用协程异步查询
  5. 分页加载:使用 Paging 3
  6. 批量操作:减少数据库交互
  7. 迁移优化:分批迁移大表
  8. 性能监控:实时监控查询性能

数据库性能优化效果

优化项优化前优化后提升
查询响应时间200ms20ms90%
写入响应时间500ms100ms80%
数据库大小50MB30MB40%

通过系统的数据库优化,可以显著提升应用响应速度,改善用户体验,减少卡顿和崩溃。