4. SQLite原生API使用与优化
4.1 SQLiteOpenHelper 原理与生命周期
SQLiteOpenHelper 是 Android 原生 SQLite 访问的核心辅助类,封装了数据库创建、版本升级和降级逻辑。开发阶段只需继承并实现 onCreate() 和 onUpgrade(),系统会按生命周期自动调度。
| 回调方法 | 触发时机 | 典型操作 |
|---|---|---|
| onCreate(SQLiteDatabase db) | 数据库首次创建时 | 执行 CREATE TABLE 语句 |
| onUpgrade(db, oldVer, newVer) | 版本号升高 | ALTER TABLE / DROP + 重建 |
| onDowngrade(db, oldVer, newVer) | 版本号降低(需覆写) | 回滚或清除数据 |
| onConfigure(SQLiteDatabase db) | getXxxDatabase() 前调用 | 设置 WAL、外键约束等 |
| onOpen(SQLiteDatabase db) | 数据库打开后 | 日志记录、校验 |
实现模板:
public class MyDbHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "app.db";
private static final int DB_VERSION = 1;
public MyDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
}
最佳实践:不要在
onCreate()/ onUpgrade() 中执行耗时查询或文件操作;使用单例模式持有 SQLiteOpenHelper 实例,避免重复创建。
4.2 CRUD 操作实现
CRUD 均通过 SQLiteDatabase 对象执行,优先使用参数化查询防注入。获取数据库实例时应统一使用 getWritableDatabase()(读写)或 getReadableDatabase()(只读)。
- 增加(Insert):
ContentValues封装字段,insert()返回新行 ID。 - 查询(Query):
query()或rawQuery()返回Cursor。 - 更新(Update):
update()返回受影响行数。 - 删除(Delete):
delete()返回删除行数。
// 插入
ContentValues cv = new ContentValues();
cv.put("name", "Alice");
cv.put("age", 28);
long newId = db.insert("users", null, cv);
// 查询
Cursor cursor = db.query("users", null, "age > ?", new String[]{"20"}, null, null, "age DESC");
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
}
cursor.close();
// 更新
ContentValues updateCv = new ContentValues();
updateCv.put("age", 29);
int rows = db.update("users", updateCv, "name = ?", new String[]{"Alice"});
// 删除
int deleted = db.delete("users", "id = ?", new String[]{"1"});
4.3 事务管理与 ACID 保障
事务保证多步操作原子性、一致性、隔离性和持久性。Android SQLiteDatabase 提供 beginTransaction()/setTransactionSuccessful()/endTransaction() 机制,支持嵌套事务(Savepoint)。
SQLiteDatabase db = dbHelper.getWritableDatabase();
try {
db.beginTransaction();
// 批量操作
db.insert(...);
db.update(...);
db.setTransactionSuccessful();
} finally {
db.endTransaction(); // 未调用 setSuccessful() 时会自动回滚
}
常见陷阱:事务内若抛出异常未捕获,务必确保
endTransaction() 在 finally 中执行;嵌套事务中外部事务的成功不会自动提交内部 savepoint,需显式 releaseSavepoint()。
4.4 批量插入性能调优
大量插入(如首次安装导入、数据同步)时逐条插入极其低效。优化方案对比:
| 方案 | 耗时(1000条) | 原理 |
|---|---|---|
| 逐条 insert() + 自动提交 | ~1200ms | 每 insert 一次写磁盘 |
| 事务包裹逐条插入 | ~60ms | 批量提交,减少 I/O |
| SQLiteStatement + 事务 | ~30ms | 预编译绑定,减少解析开销 |
| WAL 模式 + 事务 | ~20ms | 并行读写,降低锁竞争 |
推荐使用 SQLiteStatement 预编译 + 显式事务:
SQLiteDatabase db = dbHelper.getWritableDatabase();
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
db.beginTransaction();
try (SQLiteStatement stmt = db.compileStatement(sql)) {
for (User user : userList) {
stmt.bindString(1, user.getName());
stmt.bindLong(2, user.getAge());
stmt.executeInsert();
stmt.clearBindings();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
调优要点:
- 启用 WAL 模式:
db.enableWriteAheadLogging()(Android P+ 或setWriteAheadLoggingEnabled(true))。 - 每 500~1000 条提交一次事务,避免事务过大导致内存溢出。
- 批量前调用
db.execSQL("PRAGMA synchronous=OFF")可进一步加速(注意数据安全性损失)。
4.5 查询游标管理
Cursor 代表查询结果集行指针,必须及时关闭以避免 CursorWindow 泄漏。管理原则:
- 获取游标后尽快将数据读入内存(如 List),然后关闭游标。
- 禁止跨 Activity/Fragment 传递 Cursor,改用 Parcelable 或 ContentProvider。
- 使用 try-with-resources 或 finally 确保关闭。
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM users WHERE age > ?", new String[]{"18"});
while (cursor.moveToNext()) {
// 读取数据
}
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
性能陷阱:
Cursor 默认使用 CursorWindow 大小为 ~2MB,一次加载过多列或超长字符串会导致 OOM;建议只查询所需字段,避免 SELECT *。若需分页查询,使用 LIMIT ? OFFSET ?。