面试题答案
一键面试性能优化
- 数据库设计优化
- 合理设计表结构:避免冗余字段,减少数据的重复存储,遵循范式原则,如第三范式(3NF),确保数据的一致性和完整性,降低更新、插入和删除操作时的复杂性。
- 建立索引:针对查询中经常使用的WHERE子句条件字段建立索引。例如,如果经常按照用户ID查询用户信息,就对用户表的
user_id
字段建立索引。但注意索引过多会增加插入、更新和删除操作的开销,因为每次数据变动都需要更新索引。
- SQL语句优化
- 避免全表扫描:编写SQL语句时尽量利用索引,通过合理的条件筛选,减少需要扫描的数据量。例如,使用
EXPLAIN QUERY PLAN
语句分析查询计划,查看是否使用了索引以及扫描的表和行数,据此调整SQL语句。 - 批量操作:将多次小的插入、更新或删除操作合并为一次批量操作。在Android中可以使用
SQLiteDatabase
的beginTransaction()
和setTransactionSuccessful()
方法,开启事务后执行多个操作,最后提交事务,这样可以减少磁盘I/O次数,提高效率。例如:
- 避免全表扫描:编写SQL语句时尽量利用索引,通过合理的条件筛选,减少需要扫描的数据量。例如,使用
SQLiteDatabase db = getWritableDatabase();
try {
db.beginTransaction();
for (int i = 0; i < dataList.size(); i++) {
ContentValues values = new ContentValues();
// 设置数据
db.insert(TABLE_NAME, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
- 缓存机制
- 内存缓存:在应用层使用内存缓存(如
LruCache
)存储经常查询的数据库数据。当需要查询数据时,先从缓存中查找,如果找到则直接返回,避免频繁查询数据库。例如:
- 内存缓存:在应用层使用内存缓存(如
LruCache<String, Object> lruCache = new LruCache<>(maxSize) {
@Override
protected int sizeOf(String key, Object value) {
return getSizeInBytes(value);
}
};
// 获取数据时先从缓存中查找
Object data = lruCache.get(key);
if (data == null) {
// 从数据库查询
data = queryFromDatabase();
lruCache.put(key, data);
}
- **数据库缓存**:SQLite本身有一些缓存机制,如共享缓存池。可以适当调整缓存大小参数(如`PRAGMA cache_size`),根据应用的需求和设备内存情况设置合适的缓存大小,提高查询性能。但过大的缓存可能导致内存占用过高,影响其他应用功能。
资源管理
- 连接管理
- 复用连接:避免频繁创建和关闭SQLite数据库连接。可以使用单例模式管理数据库连接,在应用启动时创建连接,整个应用生命周期内复用。例如:
public class DatabaseHelper extends SQLiteOpenHelper {
private static DatabaseHelper instance;
private static final String DATABASE_NAME = "my_database.db";
private static final int DATABASE_VERSION = 1;
private DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static synchronized DatabaseHelper getInstance(Context context) {
if (instance == null) {
instance = new DatabaseHelper(context.getApplicationContext());
}
return instance;
}
}
- **及时关闭连接**:在使用完数据库操作后,确保及时关闭连接,特别是在Activity或Fragment的`onDestroy()`方法中,避免内存泄漏。例如:
@Override
protected void onDestroy() {
super.onDestroy();
if (db != null && db.isOpen()) {
db.close();
}
}
- 内存管理
- 限制查询结果集大小:在查询时尽量只获取需要的字段和行数,避免一次性加载大量数据到内存。例如,使用
LIMIT
关键字限制返回的行数,使用具体的字段名而不是SELECT *
。 - 释放资源:及时释放不再使用的数据库游标(
Cursor
)。在使用完Cursor
后,调用close()
方法关闭游标,释放内存资源。例如:
- 限制查询结果集大小:在查询时尽量只获取需要的字段和行数,避免一次性加载大量数据到内存。例如,使用
Cursor cursor = db.query(TABLE_NAME, columns, selection, selectionArgs, null, null, null);
try {
if (cursor.moveToFirst()) {
// 处理数据
}
} finally {
if (cursor != null &&!cursor.isClosed()) {
cursor.close();
}
}
多线程并发控制
- 同步机制
- 使用锁机制:在多线程环境下,为了保证数据库操作的线程安全,可以使用
synchronized
关键字或者ReentrantLock
对数据库操作进行同步。例如:
- 使用锁机制:在多线程环境下,为了保证数据库操作的线程安全,可以使用
private static final Object lock = new Object();
public void insertData(ContentValues values) {
synchronized (lock) {
SQLiteDatabase db = getWritableDatabase();
db.insert(TABLE_NAME, null, values);
db.close();
}
}
- **使用SQLiteOpenHelper的单例模式**:由于`SQLiteOpenHelper`在多线程环境下不是线程安全的,通过单例模式确保在整个应用中只有一个实例,避免多个线程同时创建和操作数据库,减少并发冲突。
2. 线程池
- 合理使用线程池:对于数据库操作相关的任务,可以使用线程池进行管理。例如,使用ThreadPoolExecutor
创建线程池,将数据库查询、插入等操作提交到线程池中执行,避免过多线程同时竞争数据库资源。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
executor.submit(new Runnable() {
@Override
public void run() {
// 数据库操作
}
});
不同优化策略对其他功能模块的潜在影响
- 性能优化策略
- 索引:过多的索引会增加数据库文件的大小,并且在插入、更新和删除操作时需要额外的时间来更新索引,可能导致这些操作的性能下降。同时,索引占用内存空间,可能影响应用的整体内存使用情况,对其他需要大量内存的功能模块(如图像处理、视频播放等)产生影响。
- 批量操作:批量操作虽然提高了数据库操作的效率,但如果批量数据量过大,可能会导致内存占用过高,特别是在内存有限的设备上,可能影响其他功能模块的正常运行。此外,如果批量操作出现错误,回滚事务可能会花费较长时间,影响应用的响应速度。
- 缓存机制:内存缓存会占用一定的内存空间,如果缓存设置过大,可能导致其他功能模块可用内存减少,影响其性能。而且缓存数据需要定期更新或淘汰,否则可能出现数据不一致问题,影响依赖缓存数据的功能模块。数据库缓存参数调整不当,可能导致内存溢出或者查询性能反而下降。
- 资源管理策略
- 连接管理:复用连接可能会导致连接长时间占用,如果其他功能模块也需要使用数据库连接但无法获取到,可能会出现等待甚至连接超时的情况。及时关闭连接如果处理不当,例如在不应该关闭连接的时候关闭了,可能导致后续数据库操作失败。
- 内存管理:限制查询结果集大小可能会导致某些功能模块获取的数据不完整,需要进行多次查询,增加了数据库负担和网络开销(如果数据是通过网络获取后存储到数据库的)。及时释放资源如果操作顺序不当,可能会导致空指针异常等问题,影响其他依赖这些资源的功能模块。
- 多线程并发控制策略
- 同步机制:使用锁机制会导致线程阻塞,降低并发性能。如果锁的粒度设置不当,可能会导致其他线程长时间等待,影响整个应用的响应速度。特别是在高并发场景下,锁竞争可能会成为性能瓶颈。
- 线程池:线程池的参数设置不合理,如线程池大小过小,可能导致任务排队等待时间过长,影响数据库操作的及时性;线程池大小过大,可能会占用过多系统资源,影响其他功能模块的运行,甚至导致系统资源耗尽。