第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 引擎 WAL模式 / 行级锁 单文件 .db / .sqlite 闪存 / eMMC DuckDB 引擎 列式存储 / 向量化 单文件 .duckdb SSD / 桌面级存储 H2 引擎 MVCC / 纯内存可选 .mv.db / .h2.db 文件 / TCP远程 信任链路差异要点 1. SQLite/DuckDB:纯本地文件IO,无网络依赖,信任边界在操作系统与文件系统 2. H2:启动TCP服务后引入网络信任层,需额外处理socket审计 3. 列存(DuckDB)对单行随机写性能低于行存(SQLite/H2)

4.4 选型权衡多维对比

维度SQLiteDuckDBH2
典型离线场景移动端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)多引擎数据同步成本两张引擎之间的一致性窗口
纯桌面端批量分析DuckDBSQLite + 视图单写者限制;不支持UDF热更新
Java生态嵌入式H2(本地模式)SQLite via JDBCJVM内存泄漏;MVCC版本链不回收
IoT边缘(C/Rust生态)SQLiteDuckDB with C-API交叉编译ABI兼容性;闪存page size

附加风险点清单:

  • SQLite:WAL-mode checkpoint饥饿 导致文件无限增长。必须在写入高峰后按策略执行 PRAGMA wal_checkpoint(TRUNCATE)。
  • DuckDB:单写者模型 不适合多进程并发写入。若桌面端的多个子进程同时打开同一数据库文件,必须使用SQLite或引入IPC代理。
  • H2:自动连接回收 缺陷。离线场景中JDBC连接如果未正确关闭,数据库文件会被残留锁文件阻塞,需手动删除 .lock.db 文件。

4.7 落地步骤(决策流程)

  1. 明确离线主场景:移动端 → SQLite(选带FTS5或JSON1扩展的编译版本);桌面端数据展示 → 分析读占比,若读>90%且涉及聚合,选用DuckDB;Java桌面 → 先确认纯本地文件模式,强制关闭H2服务器组件。
  2. 编写性能验收桩:构造真实的读写混合工作负载(事务占比、并发查询、单行更新的频率)。指标包括:TPS、P99时延、冷启动时间、峰值内存。
  3. 执行存储压力测试:写入超100万行,监控文件碎片率、WAL日志膨胀速度、Vacuum或重写开销。
  4. 使用编译时/运行时配置剪裁:禁用无关特性(网络、日志、扩展自动加载)。

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