⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

4.7.3 冲突了听谁的——冲突解决:最后写入胜利 vs 合并策略

一句话破题

冲突解决没有"最好"的策略——需要根据业务场景在"谁赢"、"合并"、"用户决定"之间做选择。

冲突解决策略

策略说明适用场景
最后写入胜利后到的覆盖先到的简单场景,数据不重要
自动合并按字段或规则合并字段独立修改
用户手动解决展示差异让用户选择复杂内容编辑
拒绝写入返回 409 要求刷新数据准确性优先

最后写入胜利(LWW)

typescript
// 简单粗暴:直接覆盖
async function updatePost(id: string, data: PostData) {
  return prisma.post.update({
    where: { id },
    data
  })
}

适用场景

  • 用户个人设置
  • 不太重要的元数据
  • 离线优先应用的同步

字段级合并

typescript
async function mergePost(
  id: string, 
  updates: Partial<PostData>,
  baseVersion: number
) {
  const current = await prisma.post.findUnique({
    where: { id }
  })
  
  if (!current || current.version === baseVersion) {
    // 没有冲突,直接更新
    return prisma.post.update({
      where: { id },
      data: { ...updates, version: { increment: 1 } }
    })
  }
  
  // 字段级合并
  const merged: Partial<PostData> = {}
  
  for (const [key, value] of Object.entries(updates)) {
    // 如果该字段未被他人修改,则采用本次更新
    // 简化逻辑:这里需要记录每个字段的修改历史
    merged[key] = value
  }
  
  return prisma.post.update({
    where: { id },
    data: { ...merged, version: { increment: 1 } }
  })
}

用户手动解决

typescript
// 检测冲突并返回两个版本供用户选择
async function updateWithConflictDetection(
  id: string,
  data: PostData,
  version: number
) {
  const current = await prisma.post.findUnique({
    where: { id }
  })
  
  if (current.version !== version) {
    // 返回冲突信息
    return {
      conflict: true,
      yourVersion: data,
      serverVersion: current,
      message: '数据已被他人修改'
    }
  }
  
  return prisma.post.update({
    where: { id },
    data: { ...data, version: { increment: 1 } }
  })
}
typescript
// 前端展示冲突解决界面
function ConflictResolver({ yourVersion, serverVersion, onResolve }) {
  return (
    <div className="conflict-modal">
      <h3>检测到冲突</h3>
      <div className="versions">
        <div className="your-version">
          <h4>你的版本</h4>
          <pre>{JSON.stringify(yourVersion, null, 2)}</pre>
        </div>
        <div className="server-version">
          <h4>服务器版本</h4>
          <pre>{JSON.stringify(serverVersion, null, 2)}</pre>
        </div>
      </div>
      <div className="actions">
        <button onClick={() => onResolve('mine')}>使用我的版本</button>
        <button onClick={() => onResolve('theirs')}>使用服务器版本</button>
        <button onClick={() => onResolve('manual')}>手动合并</button>
      </div>
    </div>
  )
}

基于时间戳的合并

typescript
interface FieldWithTimestamp<T> {
  value: T
  updatedAt: Date
}

// 每个字段都记录最后更新时间
async function mergeByTimestamp(
  id: string,
  updates: Record<string, FieldWithTimestamp<any>>
) {
  const current = await prisma.post.findUnique({
    where: { id }
  })
  
  const merged: Record<string, any> = {}
  
  for (const [field, update] of Object.entries(updates)) {
    const currentField = current[field + 'UpdatedAt']
    
    // 谁的时间戳更新,就用谁的值
    if (!currentField || update.updatedAt > currentField) {
      merged[field] = update.value
      merged[field + 'UpdatedAt'] = update.updatedAt
    }
  }
  
  return prisma.post.update({
    where: { id },
    data: merged
  })
}

业务规则优先

typescript
// 库存扣减:不能超卖
async function reduceStock(productId: string, quantity: number) {
  return prisma.$transaction(async (tx) => {
    const product = await tx.product.findUnique({
      where: { id: productId }
    })
    
    // 业务规则:库存不能为负
    if (product.stock < quantity) {
      throw new Error('库存不足')
    }
    
    return tx.product.update({
      where: { id: productId },
      data: { stock: { decrement: quantity } }
    })
  })
}

// 点赞计数:可以合并
async function incrementLikes(postId: string) {
  // 使用原子操作,不会有冲突
  return prisma.post.update({
    where: { id: postId },
    data: { likes: { increment: 1 } }
  })
}

策略选择决策树

本节小结

  • 最后写入胜利最简单,但可能丢失数据
  • 字段级合并适合字段独立修改的场景
  • 复杂内容冲突需要用户手动解决
  • 原子操作(increment/decrement)可避免数值冲突