Appearance
Android 数据库优化
目录
引言
数据库性能直接影响应用的用户体验。慢查询、数据库锁、主线程查询等问题都会导致应用卡顿甚至崩溃。本文将深入探讨 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 查询优化、索引优化、事务管理、避免主线程查询、分页加载、批量操作、数据库迁移优化和性能监控,可以显著提升数据库性能。
关键要点
- Room 查询:只查询需要的字段
- 索引优化:为查询条件列添加索引
- 事务管理:使用事务批量操作
- 避免主线程:使用协程异步查询
- 分页加载:使用 Paging 3
- 批量操作:减少数据库交互
- 迁移优化:分批迁移大表
- 性能监控:实时监控查询性能
数据库性能优化效果
| 优化项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 查询响应时间 | 200ms | 20ms | 90% |
| 写入响应时间 | 500ms | 100ms | 80% |
| 数据库大小 | 50MB | 30MB | 40% |
通过系统的数据库优化,可以显著提升应用响应速度,改善用户体验,减少卡顿和崩溃。