现场应用中的离线优先存储:SQLite 与 Realm 对比
SQLite 与 Realm 在现场离线优先存储的比较:迁移、查询选项、冲突处理、调试工具与实用选择建议。

现场应用真正需要的离线优先能力
“离线优先”不仅仅意味着“没有网络也能工作”。它意味着应用能加载有用数据、接受新输入,并将每次编辑保存好直到能同步。
现场工作带来一组可预期的限制:信号断断续续、会话时间长、设备可能较旧,并且省电模式常见。人们动作很快:打开任务、滚动长列表、拍照、填写表单,然后不去想存储细节就跳到下一个任务。
用户注意到的问题很简单。当编辑丢失、离线时列表和搜索变得缓慢、应用无法明确回答“我的工作保存了吗?”,记录在重连后重复或丢失,或一次更新导致奇怪行为时,他们就会失去信任。
因此在 SQLite 与 Realm 之间的选择,往往关乎日常行为,而不是基准测试成绩。
在选择本地数据库前,先弄清四个方面:你的数据模型会变、你的查询必须匹配真实工作流、离线同步会产生冲突,以及工具链决定了你多快能诊断现场问题。
1) 你的数据会改变
即便是稳定的应用也会演进:新增字段、重命名状态、增加界面。如果模型变更很痛苦,要么你减少迭代,要么你冒着用真实数据破坏用户设备的风险。
2) 查询必须匹配真实工作流
现场应用需要快速的筛选:例如“今日任务”、“附近场地”、“未同步表单”、以及“最近 2 小时编辑过的项目”。如果数据库让这些查询变得尴尬,界面就会变慢或代码成为迷宫。
3) 离线同步会产生冲突
两个人可能编辑同一条记录,或者一台设备可能连续数天编辑旧数据。你需要明确的规则来决定谁胜出、如何合并、哪些需要人工介入。
4) 工具很重要
当现场出现问题时,你需要能检查设备上的数据、复现问题,并在没有猜测的情况下弄清楚发生了什么。
迁移:在不打断用户的情况下改变数据模型
现场应用很少保持不变。几周后你可能会加一个复选框、重命名一个状态,或把“备注”拆成结构化字段。迁移是离线应用常常失败的地方,因为手机上已经有真实数据。
SQLite 将数据存储在表和列里,Realm 则以对象和属性存储。这个差异会很快显现:
- 在 SQLite 中,你通常编写显式的模式变更(ALTER TABLE、新表、数据拷贝)。
- 在 Realm 中,你通常提高模式版本并运行迁移函数,在对象被访问时更新它们。
两者都能轻松添加字段:SQLite 加列,Realm 在属性上设默认值。重命名和拆分则更棘手。在 SQLite 中,重命名受限于具体实现,团队常通过创建新表并拷贝数据来处理。Realm 则可在迁移时读取旧属性并写入新属性,但要注意类型、默认值和空值处理。
带有本地数据的大规模更新需要额外小心。一条重写所有记录的迁移在旧手机上可能很慢,技术人员不应该被困在停车场盯着加载动画。要为迁移时间做计划,并考虑把耗时的转换分散到多个版本中。
要公平测试迁移,把它当成同步来对待:
- 安装旧版构建,创建现实数据,然后升级。
- 测试小数据集与大数据集。
- 在迁移中断开应用再重启。
- 测试低存储场景。
- 假设可以向前升级但不能回滚。
示例:如果“equipmentId”变为“assetId”,后来又拆成“assetType”和“assetNumber”,迁移应保持旧的巡检记录可用,而不是强制登出或清空数据。
查询灵活性:你能从数据中问什么问题
现场应用的成败往往取决于列表页:今日任务、附近资产、有未处理工单的客户、本周使用的配件。你的存储选择应该让这些问题易于表达、运行快速,并且六个月后仍然易于理解。
SQLite 给你 SQL,这仍然是过滤和排序大数据集最灵活的方式。你可以组合条件、跨表连接、分组结果并在界面变慢时添加索引。如果你的应用需要“区域 A 中的资产的所有巡检,分配给团队 3,且有任一检查项失败”,SQL 往往能清晰表达。
Realm 更偏重对象和更高层的查询 API。对许多应用来说,这感觉更自然:查询 Job 对象、按状态过滤、按到期日排序、沿关系获取关联对象。权衡在于某些在 SQL 中很容易的问题(尤其是跨多重关系的报表式查询)在 Realm 中可能更难表达,或者你需要为查询重塑数据。
搜索与关系
对于跨多个字段的部分文本搜索(工单标题、客户名、地址),SQLite 往往促使你采用精心的索引或专用全文索引方案。Realm 也能过滤文本,但你仍需考虑性能以及“包含”在大规模下的含义。
关系是另一个实际痛点。SQLite 通过连接表处理一对多和多对多,这让“有这两个标签的资产”之类的模式容易实现。Realm 的链接在代码中容易导航,但多对多和“穿透查询”模式通常需要更多规划来保持读取快速。
原始查询 vs 可维护性
对维护友好的模式是保留一小组具名查询,直接映射到屏幕和报告:主列表的过滤与排序、详情页查询(一条记录加其关联记录)、搜索定义、一些计数器(徽章与离线总数)以及任何导出/报表查询。
如果你预计业务会频繁提出临时查询,SQLite 的原生查询能力难以被打败。如果你希望大多数数据访问像在操作普通对象那样,Realm 在构建速度上会更快,前提是它能在不做尴尬变通的情况下回答最难的屏幕需求。
冲突解决与同步:你能得到什么支持
离线优先的现场应用通常在断网时支持相同的核心操作:创建记录、更新记录、删除无效项。本地保存不是难点,难点在于当两台设备在同步前都更改了同一记录时如何处理。
冲突会在简单场景出现:技术人员在地下室无信号的平板上更新巡检,后来主管在办公端也修改了同一巡检。两台设备重连后,服务器收到两个不同版本。
大多数团队会采取以下一种或几种方法:
- 最后写入胜出(快速,但可能悄然覆盖有价值的数据)
- 按字段合并(当不同字段被修改时更安全,但需要清晰规则)
- 人工审查队列(最慢,但适用于高风险修改)
SQLite 提供可靠的本地存储,但它本身不提供同步功能。你通常需要自己构建其余部分:跟踪待处理操作、发送到 API、安全重试,并在服务器端强制冲突规则。
如果使用 Realm 的同步功能,Realm 可以减少一些样板代码,因为它围绕对象和变更跟踪设计。但“内建同步”仍然不会替你决定业务规则:你得自己定义什么算冲突、哪些数据能胜出。
从第一天起就规划审计轨迹。现场团队常常需要清楚回答“谁、何时、从哪台设备修改了什么”。即便你选择最后写入生效,也要存储用户 ID、设备 ID、时间戳等元数据,并在可能的情况下存储修改原因。如果你的后端是快速生成的,例如用无代码平台 AppMaster(appmaster.io),你可以在还没有成百上千台离线设备之前更早地迭代这些规则。
调试与检查:在现场问题发生前抓住它们
离线 Bug 难查,因为它们发生在你看不到应用与服务器交互的时候。你的调试体验通常归结为一个问题:你能多容易地看到设备上的内容以及它如何随时间变化?
SQLite 易于检查,因为它是一个文件。在开发或 QA 时,你可以拉出测试设备的数据库,用常见的 SQLite 工具打开、运行即席查询并导出表为 CSV 或 JSON。这有助于确认“有哪些行存在”与“界面显示什么”之间的差异。缺点是你需要理解模式、连接以及你可能创建的迁移支架代码。
Realm 在检查时更“像应用”一些。数据以对象形式存储,Realm 的工具通常是浏览类、属性和关系最简单的方式。这对发现对象图问题(缺失链接、意外的 null)很有帮助,但如果你的团队习惯于基于 SQL 的即席分析,灵活性可能较弱。
离线问题的日志和复现
大多数现场失败归因于静默写入错误、部分同步批次或只完成一半的迁移。无论哪种情况,都要投入在几项基础功能上:每条记录的“最后修改”时间戳、设备端操作日志、关于迁移与后台写入的结构化日志、在 QA 构建中启用详细日志的开关,以及一个可以导出并共享的脱敏快照动作。
示例:技术员报告完成的巡检在电池耗尽后消失。共享快照能帮你确认记录是从未写入、写入但未被查询,还是在启动时回滚。
共享失败的快照
在 SQLite 中,共享通常就是分享 .db 文件(以及任何 WAL 文件)。在 Realm 中,通常分享 Realm 文件和其旁侧文件。无论哪种方式,都要定义一个可重复的流程,在任何数据离开设备前移除敏感信息。
现实世界中的可靠性:失败、重置与升级
现场应用以平凡的方式失败:保存过程中电量耗尽、操作系统在后台杀掉应用、数周照片与日志后存储空间被占满。你选择的本地数据库会影响这些失败变成丢失工作的频率。
当在写入中发生崩溃时,只要使用得当,SQLite 和 Realm 都可以很安全。SQLite 在你用事务包裹更改时可靠(WAL 模式有助于弹性与性能)。Realm 写入默认就是事务性的,所以通常能获得“要么全部写入要么不写入”的保存,而不需要额外工作。常见风险不是数据库引擎,而是应用代码在没有清晰提交点的情况下分多步写入。
损坏很少见,但你仍需恢复计划。对 SQLite,你可以运行完整性检查、从已知良好的备份恢复或从服务器重同步重建。对 Realm 来说,一旦文件损坏通常意味着整个 Realm 文件都值得怀疑,实际恢复路径往往是“丢弃本地并重同步”(如果服务器是权威来源这没问题,但当设备持有唯一数据时就很痛苦)。
存储增长也会带来惊喜。SQLite 在删除后如果不定期执行 vacuum 会膨胀。Realm 也会增长,可能需要压缩策略,并剪裁旧对象(例如已完成的任务)以避免文件无限增长。
升级与回滚是另一个陷阱。如果一次更新改变了模式或存储格式,回滚可能会让用户停留在新文件上而无法读取。把升级视为单向操作,做好安全迁移,并提供不会破坏应用的“重置本地数据”选项。
可靠性实践带来的好处:
- 处理“磁盘已满”和写入失败时显示明确信息并提供重试路径。
- 在长表单中用检查点保存用户输入,而不仅仅在最后提交时才保存。
- 保持轻量级本地审计日志以便恢复与支持使用。
- 在数据库过大前对旧记录进行修剪与归档。
- 在低端设备上测试操作系统升级与后台杀掉场景。
示例:存储检查表和照片的巡检应用在一个月内可能触及低存储。如果应用能提前检测低空间,它就可以暂停拍照、在有网时上传并保证检查表保存安全——无论你使用哪种本地数据库。
逐步指南:如何选择并设置你的存储方案
把存储当作产品的一部分,而不是一个库的选择。最好的选项是能在信号中断时保持应用可用,并在信号恢复时表现可预测的那个。
简单决策流程
先写下你的离线用户流程。要具体:“打开今日任务、添加备注、附加照片、标记完成、采集签名。”清单上的每一项都必须在无网络时每次都能工作。
然后按短序列推进:列出离线关键屏幕以及每个屏幕需要多少数据(今日任务 vs 全历史)、勾画最小数据模型和那些无法伪造的关系(Job -> ChecklistItems -> Answers)、为每类实体决定冲突规则(不要用一个规则覆盖所有情况)、决定如何测试失败(真实设备上的迁移、同步重试、强制登出/重装行为),并构建一个带真实数据的小型原型去计时(加载、搜索、保存、离线一天后同步)。
这个流程通常会暴露真正的约束:你是需要灵活的即席查询与便捷检查,还是更看重对象化访问与更严格的模型约束?
原型中要验证的内容
用一个现实场景,例如技术员离线完成 30 次巡检然后回到有信号区域,测量首次加载 5,000 条记录时的时间,验证模式变更在升级后是否存活,统计断线后重连时出现多少冲突并确认每个冲突是否可解释,以及在支持呼叫时你能多快检查到“一条坏记录”。
如果你想在最终决定前快速验证流程,无代码原型工具可以让你先锁定工作流和数据模型,然后再决定设备端数据库方案。AppMaster(appmaster.io)是用于尽早搭建完整解决方案(后端服务、Web 管理面板和移动应用)的一个选项,然后随着需求变化再生成干净的源代码。
根据风险选择下一个验证步骤:如果表单每周变化,先测试迁移;如果多人会接触同一任务,先测试冲突;如果担心“只在办公室能工作”,优先准备现场调试流程。
常见错误,这些会损害离线优先应用
大多数离线优先失败不是来自数据库引擎,而是跳过了那些枯燥的部分:升级、冲突规则和清晰的错误处理。
一个陷阱是假设冲突罕见。在现场工作中它们很常见:两名技术员编辑同一资产,或主管在设备离线时更改检查表。如果你不定义规则(最后写入胜出、按字段合并或保留两个版本),最终会覆盖真实工作。
另一个隐蔽失败是把数据模型当成“已完成”而不演练升级。即使是小型应用也会发生模式变更。如果你不对模式进行版本控制并测试从旧版本的升级,用户在更新后可能会遇到迁移失败或空白页面。
性能问题也会很晚暴露。团队有时把全部数据都下载到设备“以防万一”,然后在中端手机上发现搜索慢、打开应用要花几分钟。
需要关注的模式:
- 没有书面的冲突策略,导致编辑被悄然覆盖。
- 迁移在全新安装时可行,但在真实升级时失败。
- 离线缓存无限制增长,查询变慢。
- 同步失败被加载动画掩盖,用户以为数据已发送。
- 调试靠猜测而不是可重复的复现脚本和样本数据。
示例:技术员离线完成一次巡检,点“同步”但没有确认。实际上上传因为认证令牌问题失败。如果应用隐藏了错误,他们会离开现场以为任务完成,信任就此丧失。
不管选哪种存储,在发布前做一个基本的“现场模式”测试:飞行模式、低电量、应用更新,以及两台设备同时编辑同一记录。如果你用无代码平台如 AppMaster 快速开发,也要把这些测试纳入原型阶段,而不是等到流程推广给无法承受停机的团队时才发现问题。
快速清单:在做决定前的核查项
在选择存储引擎前,定义何为“良好”的现场应用表现,然后用真实数据与真实设备测试。团队常为功能争论,但大多数失败来自基础:升级、慢屏、模糊的冲突规则和无法检查本地状态的手段。
把下面当作一个通过/不通过门槛:
- 验证升级:至少用两个旧版本升级到当前版本,确认数据能打开、编辑并同步。
- 在真实数据量下保持核心屏幕快速:在中端手机上计时最慢的屏幕加载。
- 为每类记录写明冲突策略:巡检、签名、耗材、评论。
- 使本地数据可检查且日志可收集:定义支持与 QA 在离线时如何捕捉状态。
- 让恢复可预测:决定何时重建缓存、重新下载或要求重新登录。不要把“重装应用”当作恢复计划。
如果你在 AppMaster 做原型,同样需要这些约束。测试升级、定义冲突并在将工作流交付给关键团队前排演恢复流程。
示例场景:信号不稳的技术员巡检应用
技术员一天开始时把 50 条工单下载到手机。每条任务包含地址、必需检查项和一些参考照片。接下来一整天信号断断续续。
每次拜访时,技术员反复编辑同几条记录:任务状态(Arrived、In Progress、Done)、使用的配件、客户签名与新照片。有些编辑小而频繁(状态),有些编辑体量大(照片)且绝不能丢失。
同步时刻:两个人都修改了同一任务
11:10,技术员离线把工单 #18 标记为 Done 并添加签名。11:40,调度员在办公室因为系统里仍显示该工单未完成而重新分配了工单 #18。技术员在 12:05 重连并上传更改。
一个良好的冲突流程不会把这件事藏起来,而是把它显性化。主管应看到一条简单提示:“工单 #18 存在两个版本”,并并排显示关键字段(状态、受派技术员、时间戳、是否有签名),提供明确选项:保留现场修改、保留办公室修改或按字段合并。
这正是存储与同步决策在现实中的体现:你能否跟踪清晰的变更历史,并在多小时离线后安全回放这些修改?
当一个任务“消失”时,调试主要是证明发生了什么。要记录足够的信息来回答:本地记录 ID 与服务器 ID 的映射(包括创建时机)、每次写入的时间戳/用户/设备、同步尝试与错误消息、冲突决策与胜出方,以及照片上传状态(与任务记录分开跟踪)。
有了这些日志,你就能复现问题,而不是凭投诉猜测原因。
下一步:快速验证,然后构建完整的现场解决方案
在陷入 SQLite 与 Realm 的争论前,先写一页纸的离线流程规格:技术员看到的屏幕、哪些数据驻留在设备上、以及在无信号时必须可用的功能(创建、编辑、照片、签名、排队上传)。
然后尽早原型整个系统,而不仅仅是数据库。现场应用往往在缝隙处失败:移动端能本地保存表单并没有意义,如果管理端无法查看与修复记录,或后端后续拒绝更新。
一个实用的验证计划:
- 搭建一个轻量级的端到端切片:一个离线表单、一个列表视图、一次同步尝试、一个管理界面。
- 做变更测试:重命名字段、把一个字段拆成两个、发布测试构建,观察升级行为。
- 模拟冲突:在两台设备上编辑同一记录,按不同顺序同步,记录出的问题。
- 练习现场调试:决定如何在真实设备上检查本地数据、日志和失败的同步负载。
- 写好重置策略:何时清空本地缓存,以及用户如何在不丢失工作的情况下恢复。
如果速度很重要,先用无代码做一次快速验证可以让你快速锁定工作流。AppMaster(appmaster.io)是构建完整解决方案(后端、管理面板与移动应用)并在需求变化时重新生成源码的一个选择。
根据风险挑下一个验证动作:若表单频繁变化优先验证迁移;若多人会修改同一任务则优先验证冲突;若担心“只在办公室能工作”则优先现场调试。
常见问题
离线优先意味着应用在没有网络的情况下仍然有用:它能加载所需数据、接受新输入,并在可以同步时保留每一次更改。关键承诺是:当信号中断、系统终止应用或电量耗尽时,用户不会丢失工作或失去信任。
当你需要复杂过滤、报表类查询、多对多关系以及使用常见工具进行即席检查时,SQLite 通常是更安全的默认选择。Realm 则适合想要对象化访问、默认事务写入,并且能将查询需求保持在 Realm 擅长范畴内的场景。
把迁移当作核心功能来对待,而不是一次性的任务。安装旧版本构建、在设备上创建真实数据,然后升级并确认应用仍能打开、编辑并同步;还要测试大数据集、低存储情况,以及在迁移过程中杀掉应用再重启的行为。
两种系统中添加字段通常都比较简单,但重命名和拆分最容易出问题。要有意地规划这些变化,设置合理的默认值,谨慎处理 null,并尽量避免在旧设备上一口气重写所有记录的迁移。
最重要的是与真实工作匹配的列表屏和过滤器:比如“今日任务”、“未同步的表单”、“最近 2 小时编辑过”的项目,以及快速搜索。如果表达这些查询很尴尬,界面要么变慢,要么代码变得难以维护。
无论选择 SQLite 还是 Realm,都不能自动解决冲突问题;你必须有业务规则。为每类实体先选定明确规则(如最后写入生效、按字段合并或人工审查队列),并让应用能够在两台设备更改同一记录时解释发生了什么。
记录足够的元数据以便解释和重放更改:用户 ID、设备 ID、时间戳,以及每条记录的“最后修改”标记。保留本地操作日志,以便看到哪些操作被排队、已发送、失败或被服务器接受。
SQLite 易于检查,因为它是一个文件,可以从设备上拉出来并直接查询,便于即席分析与导出。Realm 更便于以对象图方式浏览类、属性和关系,但对于习惯用 SQL 深入分析的团队来说,灵活性可能不如 SQLite。
数据丢失通常来自应用逻辑而非数据库引擎:比如多步写入却没有明确提交点、隐藏的同步失败、以及无应对磁盘满或文件损坏的恢复路径。使用事务/检查点、展示明确的保存与同步状态,并提供可预测的“重置并重同步”选项,而不是让用户重装应用来恢复。
在决定存储引擎前,先构建一个逼真的端到端场景并计时:成千上万条记录的首次加载、搜索、长表单保存,以及“离线一天后再重连”的同步。验证至少两个旧版本升级到当前版本的情况,模拟两台设备的冲突,并确认当问题发生时能检查本地状态与日志。


