第4章 本地存储引擎选型:SQLite / Realm / Room 对比、存储策略、性能考量
4.1 离线读写场景下的存储选型要素
在为 API 封装层设计离线读写能力时,本地存储引擎是承载缓存数据、队列请求、离线日志的核心基础设施。选型不当会直接导致读写延迟高、数据不一致、包体积膨胀或跨平台适配困难。选型需从以下维度综合评估:
- 离线读写能力——是否支持事务、冲突合并、增量同步
- 性能基准——单次读/写延迟、批量写入吞吐、并发控制
- 开发效率——API 设计是否自然、是否需手写 SQL、编译期校验
- 跨平台与生态——对 iOS / Android / Desktop 的支持、社区活跃度
- 包体积增量——对移动端安装包大小的影响
实践建议:在项目早期搭建离线读写原型,用真实业务数据对三个引擎做压测(单条插入、批量写入、混合读写),避免仅凭文档指标做决策。
4.2 三大存储引擎核心特性对比
| 维度 | SQLite | Realm | Room |
|---|---|---|---|
| 类型 | 嵌入式关系型(C 库) | 对象存储(非关系型) | SQLite 抽象层(ORM) |
| 查询方式 | 原生 SQL | 链式 API / 谓词 | DAO + @Query 注解 |
| 单条读性能 | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ |
| 批量写性能 | ⭐️⭐️⭐️(事务合并可优化) | ⭐️⭐️⭐️⭐️⭐️(延迟写入) | ⭐️⭐️⭐️(依赖 SQLite 事务) |
| 离线事务支持 | ACID 完整 | ACID + 自动回滚 | ACID(靠 SQLite) |
| 跨平台支持 | 全平台(C 绑定) | iOS / Android / JS | 主要 Android |
| 包体积增量 | ~500 KB | ~6 MB | ~200 KB(+ SQLite) |
| 学习曲线 | 中(需 SQL 基础) | 低(对象化 API) | 中(注解 + SQL 混合) |
| 推荐场景 | 标准化离线缓存、日志 | 高并发离线读写、实时同步 | Android 原生 MVVM 架构 |
注意:Realm 在 2023 年宣布逐步停止社区版维护,转向商业化授权。若团队预算有限,建议优先评估 SQLite 或 Room 的长期可持续性。
4.3 存储策略差异分析
不同引擎在离线场景下的存储策略各有侧重,直接影响数据结构设计、同步机制和冲突处理方式。
4.3.1 SQLite——原生 SQL + 事务合并
适合需要标准化 SQL 访问、跨平台复用逻辑的团队。离线写入时依赖显式事务控制:
// SQLite 离线批量写入(C API)
sqlite3 *db;
sqlite3_open("offline.db", &db);
sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0);
for (int i = 0; i < batchSize; i++) {
sqlite3_stmt *stmt;
sqlite3_prepare_v2(db,
"INSERT INTO cache(key, value, ts) VALUES(?,?,?)", -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, key[i], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, val[i], -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, now());
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
sqlite3_exec(db, "COMMIT", 0, 0, 0);
sqlite3_close(db);
- 写策略:单条写入默认自动事务,批量需手动包裹
BEGIN/COMMIT提升 10 – 20 倍吞吐。 - 冲突处理:通过
INSERT OR REPLACE或ON CONFLICT子句实现。 - 离线队列:利用自增 ID 和状态字段实现请求排队与重试。
4.3.2 Realm——对象图 + 延迟写入
Realm 采用零拷贝内存映射文件,写入先缓存到内存再异步刷盘,适合写入频繁的离线场景:
// Realm 离线写入(Kotlin)
val realm = Realm.getDefaultInstance()
realm.beginTransaction()
for (item in offlineQueue) {
val obj = realm.createObject(OfflineRequest::class.java, item.id)
obj.method = item.method
obj.url = item.url
obj.body = item.body
obj.retryCount = 0
}
realm.commitTransaction()
realm.close()
- 写策略:延迟写入,批处理提交,减少 I/O 次数。
- 冲突处理:基于主键自动合并,支持自定义冲突解决器。
- 离线队列:内置
RealmResults实时监听,数据变更自动通知同步层。
4.3.3 Room——SQLite ORM + 协程/Flow 集成
Room 在 SQLite 之上提供编译时 SQL 校验和响应式查询,适合 Android 团队:
// Room 离线缓存 DAO(Kotlin)
@Dao
interface CacheDao {
@Query("SELECT * FROM cache WHERE key = :key")
suspend fun get(key: String): CacheItem?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(item: CacheItem)
@Query("DELETE FROM cache WHERE expiry < :now")
suspend fun evictExpired(now: Long)
}
// 离线写入调用
viewModelScope.launch(Dispatchers.IO) {
cacheDao.upsert(CacheItem(key, value, System.currentTimeMillis() + TTL))
}
- 写策略:直接映射 SQLite 事务,通过
@Insert/@Update注解简化。 - 冲突处理:
OnConflictStrategy.REPLACE或ABORT。 - 离线队列:结合
Flow实现自动重试和数据刷新。
4.4 性能考量与基准数据
以下基准数据来自同一台 Android 中端设备(骁龙 778G,8GB 内存),每条记录 256 字节,1000 次操作取中位数:
| 操作 | SQLite | Realm | Room(基于 SQLite) |
|---|---|---|---|
| 单条插入 | ~1.2 ms | ~0.4 ms | ~1.3 ms |
| 批量插入(100条) | ~8.5 ms(事务合并) | ~2.1 ms | ~9.0 ms(事务合并) |
| 主键查询 | ~0.3 ms | ~0.2 ms | ~0.4 ms |
| 模糊查询(LIKE) | ~2.1 ms | ~1.5 ms | ~2.3 ms |
| 内存峰值(1000条) | ~4 MB | ~12 MB | ~5 MB |
| 数据库文件大小 | ~280 KB | ~420 KB | ~300 KB |
关键发现:
- Realm 在单条和批量写入上优势明显,但内存占用高出 2 – 3 倍。
- SQLite 与 Room 在查询性能上几乎一致(Room 有微弱 ORM 开销)。
- 包体积增量:SQLite < Room < Realm,小型项目建议优先 SQLite。
4.5 选型决策框架
综合离线读写需求、性能敏感度、团队技术栈和长期维护成本,推荐按以下流程决策:
4.6 选型建议与工程落地
- 标准化离线缓存(SQLite):如果团队跨平台(iOS + Android + Desktop)且数据模型稳定,直接使用 SQLite + 手写 SQL 可获得最佳可移植性和最小体积。
- 高并发离线读写(Realm):如果离线写入频率极高(如 IoT 设备、实时协作),Realm 的延迟写入和对象级监听能显著降低延迟。注意评估授权成本。
- Android 原生生态(Room):如果项目已使用 MVVM + LiveData / Flow,Room 是零摩擦选择。利用
@Transaction和@RawQuery可弥补批量写性能差距。
性能优化红线:无论选择哪个引擎,离线写入时务必使用批量事务(100 – 500 条/事务)并避免在主线程操作 DB。单条自动事务的开销是批量事务的 5 – 10 倍,在离线回放队列场景下会直接导致 ANR 或卡顿。
// 通用批量事务模板(以 SQLite 为例)
// 每次提交 200 条,避免单条提交的开销
int batchSize = 200;
sqlite3_exec(db, "BEGIN", 0, 0, 0);
for (int i = 0; i < total; i++) {
bindAndStep(db, items[i]);
if ((i + 1) % batchSize == 0) {
sqlite3_exec(db, "COMMIT", 0, 0, 0);
sqlite3_exec(db, "BEGIN", 0, 0, 0);
}
}
sqlite3_exec(db, "COMMIT", 0, 0, 0);