第4章 关系型数据库选型:SQLite、DuckDB、H2在移动端/桌面端/嵌入式系统中的权衡
4.1 边界与分层
本章聚焦离线架构中关系型持久化引擎的选型。所选数据库位于分层架构的数据持久层,负责提供事务性存储、结构化查询与本地数据生命周期管理。其上游为数据访问层(ORM/DAO),下游直接操作存储介质(磁盘/闪存)。
选型边界限定为三类典型离线场景:移动端(Android/iOS)、桌面端(Windows/macOS/Linux)、嵌入式系统(IoT/无界面边缘设备)。不涉及分布式、主从复制或云原生数据库。
| 分层 | 组件示例 | 职责 |
|---|---|---|
| 业务逻辑层 | Service、ViewModel | 发起读写请求、定义缓存策略 |
| 数据访问层 | DAO / Repository | 抽象查询、迁移、连接管理 |
| 持久化引擎 | SQLite / DuckDB / H2 | 事务提交、索引维护、存储格式 |
| 存储介质 | Flash / SSD / eMMC | 物理页面写入、磨损均衡 |
4.2 组件职责与核心差异
三类引擎虽然都支持SQL,但设计哲学与运行模式存在根本差异,直接影响离线架构的稳定性与性能轮廓。
- SQLite:嵌入式中型库,零配置,单文件存储,行级锁定(WAL模式下),适用于低并发、高可靠场景,Android/iOS系统级内置。
- DuckDB:嵌入式分析型数据库,列式存储,向量化执行引擎,适用于海量只读查询、批量分析,在桌面端数据分析工具中流行。
- H2:Java嵌入式数据库,支持纯内存模式与持久化模式,同时提供TCP服务器模式(非完全离线),适用于Java生态的桌面应用及单元测试。
4.3 数据流与控制流
在离线缓存场景中,典型控制流遵循请求 → 本地SQL → 结果缓存 → 渲染的闭环。数据流路径因引擎而异:
SQLite 数据流(串行化写入):
应用线程 → SQLite连接 → B-tree索引 → 页面缓存 → WAL/rollback日志 → 磁盘
DuckDB 数据流(向量化批量读):
查询解析 → 逻辑计划 → 物理计划向量块 → 列式扫描 → 压缩页面 → 异步IO
H2 数据流(多模式路径):
JDBC请求 → B-tree/Page Store → MVCC版本链 → 内存/文件映射
应用线程 → SQLite连接 → B-tree索引 → 页面缓存 → WAL/rollback日志 → 磁盘
DuckDB 数据流(向量化批量读):
查询解析 → 逻辑计划 → 物理计划向量块 → 列式扫描 → 压缩页面 → 异步IO
H2 数据流(多模式路径):
JDBC请求 → B-tree/Page Store → MVCC版本链 → 内存/文件映射
4.4 选型权衡多维对比
| 维度 | SQLite | DuckDB | H2 |
|---|---|---|---|
| 典型离线场景 | 移动端App、IoT边缘配置存储 | 桌面端ETL、日志批量分析 | Java桌面应用、单元测试 |
| 部署方式 | 静态链接库(C,各语言绑定) | 原生库或静态链接 | JAR + JDBC驱动 |
| 存储模型 | 行式B-tree | 列式压缩 | 行式B-tree / LSM混合 |
| 事务级别 | ACID,WAL支持下读未提交级别优化 | 快照隔离(MVCC) | MVCC + 可序列化 |
| 并发写入 | 单写者多读者(WAL) | 仅单进程写入(复制?无) | 多写者(需服务器模式) |
| 内存占用基线 | ~1MB空闲,与数据量线性增长 | ~20MB空闲,向量化需更多缓冲区 | JVM堆占用,最小~64MB |
| 启动耗时 | 毫秒级(无需预热) | 亚秒级(列式元数据加载) | ~1-3秒(JVM初始化 + H2预热) |
| 存储效率 | 中等,WAL碎片需vacuum | 高,列式压缩比3:1~10:1 | 中等,MVCC版本链膨胀 |
4.5 控制流与信任链路细化
在离线环境中,信任链路必须保证接收查询 → 本地执行 → 返回结果路径不存在外部依赖或安全漏洞。重点分析三个引擎的信任边界。
- SQLite:所有SQL解析与执行在进程内完成,无后台线程、无网络监听。文件系统权限即为Trust Anchor。风险点在于恶意的SQL片段(需启用SQLite权能剥离)及VFS层挂钩。
- DuckDB:与SQLite类似,无网络IO,但支持扩展插件机制(如Parquet、Postgres扫描器)。信任风险在扩展加载环节:必须禁用自动加载或签名验证。
- H2:控制流分化风险。当使用
tcp://或pg://模式时,控制流经过网络栈,引入远程攻击面。离线场景需强制使用jdbc:h2:file:...本地模式,并停用自动TCP服务。
信任链路警示: H2在Spring Boot默认配置下启动
localhost:8082 Web Console,该端口可被同一主机上的其他进程访问,不符合严格离线要求。生产环境中必须显示禁用 WEB_CONSOLE 与 TCP_SERVER。
4.6 选型决策矩阵与风险点
| 离线场景 | 首选引擎 | 次要考虑 | 主要风险 |
|---|---|---|---|
| 移动端(物理资源受限) | SQLite | — | WAL文件异常增长;跨版本schema迁移 |
| 桌面端(OLTP + OLAP混合) | SQLite(OLTP) + DuckDB(OLAP) | 多引擎数据同步成本 | 两张引擎之间的一致性窗口 |
| 纯桌面端批量分析 | DuckDB | SQLite + 视图 | 单写者限制;不支持UDF热更新 |
| Java生态嵌入式 | H2(本地模式) | SQLite via JDBC | JVM内存泄漏;MVCC版本链不回收 |
| IoT边缘(C/Rust生态) | SQLite | DuckDB with C-API | 交叉编译ABI兼容性;闪存page size |
附加风险点清单:
- SQLite:WAL-mode checkpoint饥饿 导致文件无限增长。必须在写入高峰后按策略执行
PRAGMA wal_checkpoint(TRUNCATE)。 - DuckDB:单写者模型 不适合多进程并发写入。若桌面端的多个子进程同时打开同一数据库文件,必须使用SQLite或引入IPC代理。
- H2:自动连接回收 缺陷。离线场景中JDBC连接如果未正确关闭,数据库文件会被残留锁文件阻塞,需手动删除
.lock.db文件。
4.7 落地步骤(决策流程)
- 明确离线主场景:移动端 → SQLite(选带FTS5或JSON1扩展的编译版本);桌面端数据展示 → 分析读占比,若读>90%且涉及聚合,选用DuckDB;Java桌面 → 先确认纯本地文件模式,强制关闭H2服务器组件。
- 编写性能验收桩:构造真实的读写混合工作负载(事务占比、并发查询、单行更新的频率)。指标包括:TPS、P99时延、冷启动时间、峰值内存。
- 执行存储压力测试:写入超100万行,监控文件碎片率、WAL日志膨胀速度、Vacuum或重写开销。
- 使用编译时/运行时配置剪裁:禁用无关特性(网络、日志、扩展自动加载)。
4.8 验收标准
| 验收项 | 通过条件 | 验证方式 |
|---|---|---|
| 事务原子性 | 进程KILL后不丢失已提交数据 | 注入点KILL测试 |
| 并发读写隔离 | 多线程写入不破坏数据库完整性 | SQLite PRAGMA integrity_check |
| 存储占用上限 | 数据量1M行时文件大小不超过基线50% | 对比引擎理论压缩比 |
| 无网络依赖 | 关闭所有HTTP/TCP端口,数据库中无监听套接字 | netstat / ss / lsof |
| 迁移兼容性 | 同一份Schema在三种引擎的DDL可互转 | DDL脚本联调 |
4.9 配置示例(关闭非离线特性)
以下配置确保引擎运行在安全离线模式。
-- SQLite 关闭WAL模式(仅用于极端低资源环境)
PRAGMA journal_mode=DELETE;
PRAGMA synchronous=FULL;
PRAGMA automatic_index=OFF;
-- DuckDB 禁止扩展自动加载
SET enable_progress_bar=false;
SET threads=1;
SET default_null_order='nulls_first';
-- H2 本地文件模式(禁止TCP、Web、PG)
jdbc:h2:file:/data/cache/myapp;MODE=SQLite;AUTO_SERVER=FALSE;
DB_CLOSE_DELAY=10;IGNORE_UNKNOWN_SETTINGS=TRUE