为什么 “majority” 不是 MongoDB 的默认 read concern?

发布: (2025年12月31日 GMT+8 16:58)
10 min read
原文: Dev.to

Source: Dev.to

TL;DR

如果你习惯于传统的 SQL 数据库和同步的请求‑响应流程——在同一个事务或会话中读取你的写入——请在 MongoDB 中使用 majority 读取关注。它提供了你能从数据库中期望的最高隔离性和持久性。

  • 它不是默认设置,但将其改为你的连接是安全的。
  • 默认设置针对事件驱动、微服务架构的异步通信进行了优化,即使这意味着有时会读取可能随后被回滚的状态,也更倾向于更低的延迟。

为什么 MongoDB 默认不启用最强一致性

PostgreSQL 用户通常期望写入只有在被确认后才对其他会话可见,无论是通过自动提交的 DML 还是显式的 COMMIT

在 MongoDB 中,必须 启用 majority 读取关注(read concern) 才能实现类似的 ACID 保证,而这 并非默认设置

看似令人惊讶的是,MongoDB 在分布式数据库中提供了最强的一致性选项——完整的 ACID 语义,却没有默认开启,尽管其性能影响似乎并不大。

这种差异促使我们进一步探讨其背后的设计理由。

历史背景:SQL 与 NoSQL

  • SQL 标准:隔离级别最初是根据并发会话在读取和写入相同数据时可能出现的 异常(现象)来定义的。

    • 这些定义与 基于锁的实现 紧密关联,而不是抽象模型;它们假设读取和写入使用锁,并且活动事务共享单一的当前数据库状态。
  • 现代数据库 通常会选择不同的设计以实现可扩展性:

设计示例关键特性
使用 MVCC 的非阻塞读取PostgreSQL、MongoDB读取看到的是快照;会出现 写偏斜 等异常;支持如快照隔离(Snapshot Isolation,SI)等隔离级别。PostgreSQL 将其 SI 级别称为 可重复读,以匹配 SQL 标准。
非阻塞写入(乐观并发)MongoDB写冲突会立即被检测到并抛出可重试异常,而不是等待锁。

Source:

MongoDB 中的隔离性与持久性

要理解 MongoDB 中的隔离性和持久性,必须先分别考虑 写关注点——尤其是在复制的、分布式的环境中,读写可能落在不同的服务器上。随后我们再看看在写入后进行读取时,它们是如何相互作用的。

隔离性 vs. 持久性(ACID 中的 ID

概念它保证的内容
隔离性不同会话之间的读写如何相互可见。它必须在事务完成之前隐藏未提交写入的中间状态,并防止出现遗漏已提交写入的陈旧读取。
持久性数据一旦写入后就保持持久,即使发生故障也不会丢失。已经被读取的数据也必须保证保持持久。

最初这些定义是基于单节点数据库的。 在现代系统中,持久性还必须应对网络和数据中心故障,因此数据会跨多个节点持久化,而不仅仅保存在本地磁盘上。

典型提交流程(SQL 风格)

  1. 提交被发起
  2. WAL 刷写到本地磁盘本地持久性
  3. WAL 刷写到远程磁盘全局持久性
  4. 更改对其他会话可见(隔离结束)。
  5. 在客户端会话中确认提交

上述顺序对应:

  • PostgreSQL 中 synchronous_commit = on
  • MongoDB 中 w:majority 并且 读取会话使用 majority 读关注点。

其他配置会重新排列这些步骤:

系统 / 设置顺序差异说明
Oracle(默认)在 redo 日志刷写之前就让更改可见(除非设置 paranoid_concurrency_mode)。
PostgreSQL synchronous_commit = local MongoDB w:1在全局持久性之前就返回确认。
MongoDB local 读关注点数据在持久化之前就已经可见。

“缺失”的默认设置:为何不使用最强的序列?

最强的隔离‑和‑持久性序列(上述步骤 1‑5)并不是 MongoDB 的默认设置。原因之一是 SQL 标准从未描述的额外异常,因为它假设 对单一数据库状态的读锁和写锁是互斥的

MVCC 引入了两个逻辑时间

  1. 读取时间 – 事务的开始(或在 Read Committed 中语句的开始)。所有读取都使用该时间的快照。
  2. 写入时间 – 事务的结束;所有写入在提交时原子出现。

因为 读取时间

关键要点: 在 MVCC 系统中,应用程序常常在写入确认到达之前 通知其他服务,假设写入已经完成。只有当读取关注确保写入既持久又可见时,这种期望才是安全的。

摘要

  • majority 读取关注 在 MongoDB 中提供最强的隔离性和持久性,可与许多 SQL 数据库的默认行为相媲美。
  • 它不是默认设置,因为 MongoDB 面向 事件驱动、低延迟的微服务架构,在这种场景下,偶尔出现的陈旧读取是可以接受的权衡。
  • MVCC(读取时间 …)引入的 因果异常

提示: 使用 w:1 时,选择 local 读取关注 以避免此异常。w:1 不是 默认值;仅在希望降低延迟而非保证持久性时才会选择它。

延迟 vs. 持久性 权衡

  • 本地确认 (w:1 / synchronous_commit = local)

    • 优点: 延迟更低;写入快速完成。
    • 缺点: 发生故障时丢失事件的风险更高(写入可能被回滚)。
  • 多数确认 (w:majority / synchronous_commit = on)

    • 优点: 更强的持久性;写入能在节点或数据中心故障后仍然存活。
    • 缺点: 需要额外的网络延迟,因为系统要等待远程确认。

即使使用多数确认,如果微服务 B 在多数已提交写入之前读取,也可能出现读后写异常。使用 MongoDB 的 本地读取关注 可以消除该异常,但可能会暴露随后可能被回滚的数据。

默认读取关注及其原理

  • 默认读取关注(对 MongoDB 通常是 “majority”)与事件驱动架构高度契合,而事件驱动架构是早期 NoSQL 系统的主要使用场景。
  • 开发者通常期望读取返回 最新的更改,即使这些更改尚未在原始线程中完全确认。

传统架构

当 MongoDB 在更传统、注重持久性的部署中使用时:

  • 首选 “majority” 读取关注
  • 不会产生额外的性能惩罚,因为写入延迟已经在等待写入确认时付出了代价。

“majority” 的工作方式:

  • 将读取时间戳设置为 最后提交时间,同时保持读取本地化。
  • 在极少数情况下可能会短暂阻塞(例如实例启动、回滚、不可用或落后的从节点)。
  • 通常 不会产生明显的性能影响

MongoDB的可配置一致性模型

与许多 SQL 数据库对每个 DML 操作强制统一的一致性保证不同,MongoDB 将更多的责任交给开发者,让他们自行选择合适的一致性和性能设置。

关键配置选项

  1. Write Concern – 决定持久性

    • 示例:w:majority 用于抵御网络或数据中心故障的弹性。
  2. Read Concern – 决定读取的可见性保证

    • 示例:
      • majority – 读取最近的多数提交数据。
      • snapshot – 为多分片事务提供更强的一致性。
  3. Read Preference – 决定读取的路由位置

    • 允许在副本集之间扩展读取,在可接受的情况下容忍一定的陈旧度。

通过调节这三个旋钮,MongoDB 能够适配各种一致性和性能需求。

Back to Blog

相关文章

阅读更多 »

分布式系统中的双写问题

概述:双写问题发生在单个逻辑操作必须更新两个或多个独立系统时——例如,将数据持久化到数据库并发布…

握手:现代 API 的隐形成本

发生了什么?当在本地 API 响应时间为 20 ms 时,日志没有指向瓶颈,数据库已经优化,代码也很干净。准备好进行…