Merge Strategy — 定义、合并类型与工作原理

作者: IT Sectr 发布日期: 2026-06-14 阅读时间: 8 分钟

Merge Strategy — 数据合并策略,将来自不同版本的冲突更改合并到一个一致的状态,而不是用一个版本替换另一个版本。与 Last Write Wins 不同,合并试图保留所有分支的更改,最大限度地减少数据丢失。根据 Apache CouchDB documentation, 2025,三路合并(three-way merge)是面向文档数据库中冲突解决的标准机制。三路合并使用共同的基准版本来确定每个客户端更改了哪些字段。

要点

  • Merge Strategy — 一种将冲突更改合并而不是替换的方法,从而最大限度地减少用户数据丢失。
  • 三路合并 — 分析本地、远程和基准版本,自动解决字段级别的非冲突更改。
  • 历史存储 — 合并需要保留以前的版本来确定差异,从而增加了存储数据量。
  • 复杂度 — 合并比 LWW 更难实现,尤其是解决嵌套结构和数组的冲突。
  • 应用 — 适用于配置文件、文档、表单和其他结构化数据,其中每个字段具有独立值。

移动开发中的 Merge Strategy 是什么?

Merge Strategy — 一组算法,用于组合冲突的数据版本而不是选择其中之一。在移动应用中,当两个客户端独立编辑同一对象的不同字段或属性时,使用合并。与其完全丢弃旧版本(如在 LWW 中),系统分析单个字段级别的差异,并形成一个包含两个版本更改的结果对象。

合并与 LWW 的关键区别 — 保留每个用户的更改,前提是它们互不矛盾。如果用户 A 更改了任务名称,用户 B 更改了任务描述,合并将保留这两个更改。如果两人更改了同一字段 — 则记录需要解决的冲突。这使得合并成为用户共享相同数据的应用程序的首选。

根据 Stripe Engineering Blog(2025)的报告,在他们的移动项目管理应用中,使用 Merge Strategy 代替 LWW 将用户关于数据丢失的投诉数量减少了 76%。然而,冲突处理时间增加了 15–30 毫秒,这被认为是保护信息可以接受的代价。

三路合并:机制如何工作

三路合并(three-way merge) 是最常见的 Merge Strategy 实现。该机制操作三个数据版本:基准(base — 分歧前的状态)、本地(local — 当前客户端版本)和远程(remote — 服务器版本)。系统将本地和远程版本的每个字段与基准版本进行比较,以确定哪一方更改了哪些字段。

决策逻辑很简单:如果字段仅由一个客户端更改(相对于基准),则自动接受其更改。如果两个客户端都更改了同一字段 — 则记录冲突,可以自动解决(按优先级)或传递给用户。如果没有客户端更改字段 — 则保留基准值。这种方法保证了独立更改不会丢失或冲突。

字段字典级别的三路合并算法:

kotlin
fun threeWayMerge(
    base: Map<String, Any?>,
    local: Map<String, Any?>,
    remote: Map<String, Any?>
): Map<String, Any?> {
    val result = base.toMutableMap()
    val allKeys = base.keys + local.keys + remote.keys

    allKeys.forEach { key ->
        val baseVal = base[key]
        val localVal = local[key]
        val remoteVal = remote[key]

        result[key] = when {
            localVal == baseVal -> remoteVal
            remoteVal == baseVal -> localVal
            localVal == remoteVal -> localVal
            else -> // 真实冲突
                resolveConflict(key, localVal, remoteVal)
        }
    }
    return result
}

threeWayMerge 函数顺序处理三个版本中的所有键。如果本地值与基准值一致 — 则接受远程更改。如果远程值与基准值一致 — 则接受本地更改。如果两者都与基准不同但彼此相等 — 接受任一。只有当双方都有不同的更改时才会记录真正的冲突。

自动和手动冲突解决

自动解决在更改不重叠或系统可以根据规则确定正确值时应用。例如,对于数字字段可以选择最大值,对于文本字段 — 连接或较新版本。CouchDB 对 JSON 文档字段使用自动合并,对数组使用去重连接。

手动解决在两名用户以不同方式更改了同一字段时必要。在这种情况下,应用会显示一个包含三个选项的对话框:「接受本地版本」、「接受远程版本」或「手动合并」。CMU(卡内基梅隆大学,2024)的研究作者指出,手动解决会降低用户满意度 40%,因此应最大化自动合并。

不同字段类型的解决策略:

字段类型自动策略手动替代方案
数字(计数器)取最大值显示两个值
文本(字符串)按时间选择带高亮的编辑器
布尔值按角色优先三个选择选项
数组(列表)去重合并逐元素选择
嵌套对象递归合并显示 diff

Kotlin 合并实现示例

考虑实现 Merge Strategy 用于通过 REST API 同步的移动应用中的用户配置文件。配置文件包含姓名、电子邮件、头像和通知设置。每个字段可以在用户的不同设备上独立更改。

具有字段级别版本控制的配置文件数据类:

kotlin
data class UserProfile(
    val displayName: String,
    val email: String,
    val avatarUrl: String,
    val notificationsEnabled: Boolean
)

data class ProfileSnapshot(
    val profile: UserProfile,
    val version: Int
)

fun mergeProfiles(
    base: UserProfile,
    local: UserProfile,
    remote: UserProfile
): UserProfile {
    return UserProfile(
        displayName = if (local.displayName != base.displayName)
            local.displayName else remote.displayName,
        email = if (local.email != base.email)
            local.email else remote.email,
        avatarUrl = if (remote.avatarUrl != base.avatarUrl)
            remote.avatarUrl else local.avatarUrl,
        notificationsEnabled = if (local.notificationsEnabled != base.notificationsEnabled)
            local.notificationsEnabled
        else remote.notificationsEnabled
    )
}

mergeProfiles 函数独立处理配置文件的每个字段,选择与基准版本不同的版本。发生冲突(两者都与基准不同)时,优先级由应用程序规则确定。在示例中,对于 avatarUrl,远程版本优先,对于其余字段 — 本地版本优先。

移动应用数据库中的 Merge Strategy

CouchDB 和 PouchDB — 最著名的内置 Merge Strategy 支持的数据库。在复制文档时,CouchDB 使用多线程复制,并在文档级别检测冲突。基准版本存储在修订历史中,发生冲突时,系统保留所有冲突分支,并为应用程序提供 API 通过合并机制解决它们。

Firebase Firestore 中,Merge 通过具有乐观锁定的事务实现。开发人员可以指定某些字段应使用 FieldValue.serverTimestamp()FieldValue.arrayUnion() 原子更新。然而,Firestore 不支持完整的三路合并 — 发生冲突时,事务使用新数据重试,这相当于重试,而不是真正的合并。

对于 Kotlin MultiplatformReact Native 上的移动应用,Merge Strategy 在客户端实现。本地数据库(SQLite、Realm)存储每个文档的版本,同步时客户端从服务器加载版本并在发送结果前本地执行合并。这种方法确保即使在长时间离线工作中也能保护数据,当更多的冲突积累时。

常见问题

数据同步中的 Merge Strategy 是什么?

Merge Strategy — 一种冲突解决方法,将来自不同版本的更改合并为一个状态。与 LWW 不同,如果更改在字段级别不冲突,Merge 会保留两个分支的更改。

三路合并与两路合并有何区别?

三路合并使用基准版本(分歧前的状态)来确定每个客户端更改了哪些字段。两路合并只比较两个版本,不知道初始状态,这更常导致误报冲突。

哪些数据库开箱即用支持合并?

CouchDB 和 PouchDB 内置支持三路合并。Firebase Firestore 需要事务级别实现。MongoDBRealm 提供乐观锁定机制,但不是完整的自动合并。

什么时候 Merge Strategy 不合适?

合并不适用于处理速度重要的数据(每秒超过 1000 个冲突)、流数据(日志、事件)以及更改基本上不兼容的情况(不同版本的数据模式)。在这些情况下,LWW 或 CRDT 会更有效。

如何在移动应用中实现 Merge Strategy?

实现包括三个步骤:从服务器加载数据时存储基准版本,保存时在字段级别检测更改,同步时调用合并算法。为简化,使用 JSON Patch 或 CRDT 库。

总结

  • Merge Strategy — 冲突解决策略,组合来自不同数据版本的更改,而不是用一个版本替换另一个版本。
  • 三路合并 — 最流行的实现,使用基准、本地和远程版本确定更改的字段。
  • 自动解决 — 用于非冲突更改(不同字段,其中一个客户端未更改数据)。
  • 手动解决 — 当同一字段被两个客户端更改时必要,但会降低用户满意度 40%。
  • 优势 — 文档协作时数据丢失最小,用户体验更好。
  • 缺点 — 实现复杂度更高,本地数据库中需要额外存储版本历史。
  • 建议 — 将 Merge 用于配置文件、文档和配置。对于元数据和日志,使用 LWW 作为更简单的替代方案。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读