更新记录

1.0.0(2026-11-05)

首个版本:鸿蒙 relationalStore SQLite 内核 + 防回退双副本自愈(官方 backup 副本 / filesDir 耐久副本 / seq 三方裁决自动恢复);提交零阻塞(1.2s 防抖后台维护 + seq 内存缓存);UTS Promise 边界包装规避链式吞值;onDiag 诊断全程留痕。


平台兼容性

uni-app x(5.25)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × × × 12 ×

鸿蒙 SQLite 防回退(杀后台数据不丢)· 双副本自愈本地数据库

鸿蒙本地数据库 UTS 插件(app-harmony,API 12+),内置对抗鸿蒙系统级「杀后台整目录回退」的双副本自愈机制:官方 backup 副本 + filesDir 耐久副本 + seq 三方裁决 + 库外提交标记,提交链零阻塞。真机实测:杀后台 → 重进,数据不丢。

一个真实的事故(本插件的由来)

这是从一个日活记账 App 的真实线上事故中打磨出来的方案,不是实验室代码:

用户反馈「记账数据全没了」。文件级取证(把设备沙盒里的数据库文件直接拉出来解析)发现:写盘根本没坏,每一笔都提交成功。真凶是——

鸿蒙 relationalStore 在进程被杀后台后重开时,会把整个 rdb 目录(主库 + WAL + 官方 backup 出的 .bak)回退到本次会话开始前的旧快照。 已提交的数据直接消失。

  • PRAGMA wal_checkpoint 无效——回退发生在 relationalStore 服务层,不在 SQLite 层;
  • 官方 backup() 出的副本与主库同目录,一起被回退;
  • 断网时没有服务器同步兜底,用户看到的就是「记的数据全没了」。

这个行为不报错、不留日志、只在「杀后台 + 断网」组合下出现,是最隐蔽的一类数据丢失。市面上的鸿蒙 SQLite 插件都是 relationalStore 的能力封装,都不处理这个问题——因为不亲身踩一遍根本不知道它存在。

与常见 SQLite 插件的定位区别

常见 SQLite 插件 本插件
基础 SQLite 能力 ✓ ✓(relationalStore,列类型原生保真)
杀后台整目录回退 ✗(无处理,回退照丢) ✓ 自动裁决 + 自动恢复
副本防呆 ✗(backup 异步落盘可能拷出空副本) ✓ 尺寸守卫 + 原子拷贝 + 延迟重试
提交性能 取决于用法(同步等备份会卡提交) ✓ 防抖后台维护,提交链零阻塞
UTS Promise 吞值坑 ✗(需要你自己踩) ✓ 边界已全部包装,开箱即用
失败留痕 多数静默失败 ✓ onDiag 全程留痕,丢了数据可查
安卓 / iOS / Web 部分支持 ✗(明确不做,见下方边界)

一句话:它们卖的是「能存」,我们卖的是「杀后台也还在」。

适合你,如果

  • 你的 App 数据只存本地(记账、草稿、离线单据、聊天记录、收藏),丢一条都是客诉;
  • 你的用户是鸿蒙设备,且断网使用是常态(这正是回退最凶的场景);
  • 你试过 checkpoint / 事务 / 官方 backup,杀后台后数据仍然丢,查不出原因;
  • 你想自己写 SQL,只要一个可靠的内核 + 防回退兜底,不需要 ORM。

四道防线(怎么做到的)

写事务 → z_dbseq 盖 seq(与数据同事务提交,回退时 seq 同步回退)
       → commit
       → 库外标记写同一 seq(uni storage,不在 SQLite 回退范围)← 提交链到此返回,零阻塞
       → 1.2s 防抖后台维护:官方 backup 副本 → 拷 filesDir 耐久副本
  1. rdb 同目录官方副本:st.backup() 的产物是另一个文件,不在系统「异常终止快照回退」作用范围;
  2. filesDir 耐久副本:系统只回退 rdb 目录,filesDir(应用沙箱)不受影响——这是最终恢复源;
  3. seq 三方裁决:启动时比对 主库 seq / 副本 seq / 库外标记,判定「发生过回退且副本更新」→ 自动从副本 restore 主库,恢复结果二次校验;
  4. 完整性守卫:st.backup 疑似先建空文件、异步补写、Promise 提前 resolve(真机实证副本曾拷成 4096B 空库)——尺寸比对 + 原子拷贝(先 .tmp 后 rename)+ 500ms 延迟重试兜底。

所有失败路径(备份/checkpoint/裁决/恢复)通过 onDiag 回调留痕,绝不静默吞错。

快速上手(3 行起步)

import { createSafeSqlite } from '@/uni_modules/mqy-safesqlite/js/safe-sqlite'

// App onLaunch 初始化一次
const db = createSafeSqlite({
  dbPath: 'app.db',              // 库文件名
  extKey: 'myapp_db_seq',        // 库外标记的 storage 键(多库实例各用各的)
  onDiag: (act, detail) => console.log('[safesqlite]', act, detail)
})

await db.open()                  // 打开 + 自动启动裁决(检测到回退自动恢复)

await db.tx([
  'CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, text TEXT)',
  "INSERT OR REPLACE INTO notes VALUES ('n1', 'hello')"
])
const rows = await db.select('SELECT * FROM notes')   // 对象数组

五分钟验证(不用信我,信你的设备)

  1. await db.tx(["INSERT ..."]) 写几条数据;
  2. 上滑划卡杀掉 App;重新打开 → 数据还在;
  3. 开飞行模式重复 1-2(最严格场景)→ 数据同样在;
  4. onDiag 看 boot_seq:主库 / 副本 / 库外标记三方一致 = 机制在工作;
  5. demo 目录有现成演示页(写一条 / 刷新 / 手动维护 / 诊断日志),拷进工程即可跑。

API

createSafeSqlite(options)

选项 默认 说明
dbPath 'app.db' 库文件名(relationalStore 只认文件名,落应用 database 目录)
extKey 'safesqlite_db_seq' 库外提交标记的 uni storage 键,多库实例必须各用各的键
maintainDelay 1200 提交后副本维护防抖毫秒(连续写入自动合并成一次)
docRetryMax 4 耐久副本不完整时的重试上限
onDiag null (act, detail) => void 诊断回调,建议接你的日志
core null 极少数工程 @ 别名解析失败时,手动注入 UTS 内核

实例方法

方法 说明
open() 打开库 + 启动裁决(幂等)。裁决任何异常都放行启动,不卡死 App
tx(sqls: string[]) 事务执行 SQL 数组:自动 begin/commit/rollback、盖 seq、写库外标记、调度副本维护
exec(sql) 执行单条写 SQL(自动盖 seq,非事务)
select(sql) 查询,返回对象数组(INTEGER/REAL→number,TEXT→string,NULL→键缺失)
maintain() 手动触发一次 checkpoint + 双副本刷新(一般不需要,自动调度已覆盖)
close() 关闭库(自动取消未触发的维护定时器)
stamp() 读库外标记当前值(诊断用)

顶层导出

导出 说明
isHarmonyBackend() 当前是否鸿蒙环境(安卓/iOS 请分流到 plus.sqlite)
VERSION 插件版本

边界与局限(先说清楚,免得买错)

本插件不做的事,以及对应解法:

  1. 仅鸿蒙 App(app-harmony,API 12+)。安卓/iOS 没有回退问题,请继续用 plus.sqlite(本插件提供 isHarmonyBackend() 供分流);Web/小程序不支持。
  2. 不含 ORM / Schema Builder / 迁移 / 参数绑定。定位是「可靠内核 + 防回退安全层」,SQL 由你拼。拼接字符串值时请自行转义单引号(或用本插件只处理已知安全值);需要完整 ORM/迁移能力的请选结构化数据库插件。
  3. 不提供整库加密。敏感字段请在写入前由业务层加密(SQLCipher / HUKS 等方案与本插件不冲突)。
  4. 不含云端同步。防回退解决的是「本地不丢」;换机/多端请配合你自己的服务端同步。
  5. 极端窗口可能丢最后一两笔:提交后 1.2 秒内立即杀后台且副本未刷新时,恢复的是上一个副本快照。此时启动裁决会以 restore_partial 诊断明确报告差了几笔,业务可据此向服务端补拉。把 maintainDelay 调小可收窄该窗口(代价是副本刷新更频繁)。
  6. uts 插件改动需重新打包:本插件含原生 UTS 代码,升级后需重新制作自定义基座/重新打包,不能只热更 WGT。
  7. 验证设备说明:真机验证基于 HarmonyOS 真机(API 18+ SDK)。不同 SDK 版本的 relationalStore 行为可能有差异,收费插件均支持免费试用,建议先试用跑完上面的五分钟验证再购买。
  8. 隐私:不申请系统权限、不采集数据、无广告,数据全部留在应用沙盒内。

FAQ

Q:我用了官方 backup,为什么数据还是丢? A:因为官方 backup 的副本与主库同在 rdb 目录,系统回退是整目录回退,副本一起退。必须把副本拷到回退范围之外(filesDir),并在启动时按标记裁决——这正是本插件做的事。

Q:PRAGMA wal_checkpoint(TRUNCATE) 不是能防丢吗? A:真机取证证明它对抗不了服务层整目录回退(checkpoint 后杀后台照样回退)。本插件仍保留 checkpoint(让主文件尽量持全量),但它只是辅助,不是防线。

Q:为什么我直接 import UTS 内核后,链式 .then 拿到的值是 undefined? A:UTS Promise 的 .then 回调会触发,但链式返回值被吞成 undefined(真机实证的通用坑)。必须先在边界包成原生 JS Promise(jsWrap,实现见 js/safe-sqlite.js)。用本插件 JS 层则无需关心。

Q:多库实例怎么配? A:每个库一个 createSafeSqlite 实例,dbPath 与 extKey 各用各的,互不干扰。

Q:恢复语义是什么?会恢复成多旧的版本? A:恢复源是「最后一次成功提交并完成副本刷新」的快照,正常滞后 ≤ 维护周期(默认 1.2s)。裁决与恢复结果都有诊断事件(restored / restore_partial / rollback_detected),丢了多少笔一查便知。

Q:能和服务器同步共存吗? A:不但共存,而且互补——本插件保「本地不丢」,你的同步保「换机/多端一致」。rollback_detected(回退但副本同旧)事件就是「本地与副本都救不回」的明确信号,此时向服务端补拉即可。

关键词

uni-app sqlite / uni-app x sqlite / HarmonyOS sqlite / 鸿蒙数据库 / relationalStore / 鸿蒙本地存储 / 杀后台数据丢失 / 数据库防回退 / SQLite backup / 本地数据库 / 离线存储 / UTS 插件

隐私、权限声明

1. 本插件需要申请的系统权限列表:

无。本插件仅通过 HarmonyOS relationalStore API 访问应用沙箱内的本地数据库文件,全部读写均发生在应用自身数据目录内,不申请任何系统权限。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据。插件完全离线运行,不连接任何服务器、不上传任何数据,所有数据库操作仅在用户设备本地完成。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无。本插件不包含任何广告、推广及第三方 SDK。

暂无用户评论。