bigywater.tech
MongoDB · 60

读关注:local、majority、snapshot 各读到什么

读关注到底决定读到什么?majority 为什么读不到刚写的数据?linearizable 代价多大?这篇在 3 节点副本集上先测健康时的延迟,再 kill 两个 secondary 制造“未被多数确认”的窗口,逐个读关注对比。

NEW 对 old
primary 上 w:1 写入、未被多数确认时,local 与 majority 读到的值
4.8 对 0.1 ms
linearizable 与其它读关注的单次读延迟
code 50
linearizable 在多数不可用时 max_time_ms 超时的错误码

健康时

实测 · 本机执行输出
  local         读到 'old';中位数 0.15 ms,p99 0.45 ms
  available     读到 'old';中位数 0.10 ms,p99 0.28 ms
  majority      读到 'old';中位数 0.09 ms,p99 0.20 ms
  snapshot      读到 'old';中位数 0.10 ms,p99 0.22 ms
  linearizable  读到 'old';中位数 4.81 ms,p99 8.05 ms

local、available、majority、snapshot 的读延迟几乎一样(约 0.1 ms);linearizable 要确认仍是 primary 且被多数认可,约 4.8 ms。

未被多数确认的写入

实测 · 本机执行输出
  已 kill 两个 secondary,并在 primary 上用 w:1 把 v 改成 "NEW"
  readConcern local    → v = 'NEW'
  readConcern majority → v = 'old'
  readConcern linearizable(max_time_ms=3000):3002 ms 后 ExecutionTimeout(code 50): operation exceeded time limit (configured timeouts: connectTimeoutMS: 20000.0ms), full err
  同时 w:majority 的写:WTimeoutError(code 64)

kill 两个 secondary 后,primary 上 w:1 写入的值,local 读得到,majority 读到旧值,linearizable 等不到多数而超时(code 50)。

primary 降级后

实测 · 本机执行输出
  10 秒后 primary 因看不到多数降级:isWritablePrimary = False
  已降级的节点上 readConcern local    → v = 'NEW'
  已降级的节点上 readConcern majority → v = 'old'

10 秒后 primary 因看不到多数而降级;local 读仍然是 NEW,majority 仍是 old。

恢复之后

实测 · 本机执行输出
  恢复后 readConcern local    → v = 'NEW'
  恢复后 readConcern majority → v = 'NEW'

secondary 恢复、写入被复制到多数后,majority 读也看到了 NEW。w:majority 超时的写入最终也生效。

snapshot

实测 · 本机执行输出
  事务外 find 带 readConcern snapshot: {'_id': 1, 'v': 'NEW', 'x': 1}
  事务内 snapshot 读: {'_id': 1, 'v': 'NEW', 'x': 1}

事务里的默认读;事务外 find 指定 snapshot 在这个版本上也能执行。

默认值

实测 · 本机执行输出
  # getDefaultRWConcern
  test> db.adminCommand({getDefaultRWConcern: 1}).defaultReadConcern
  { level: 'local' }
  # explain 里的读关注
  test> db.t.find({_id: 1}).readConcern("majority").explain().command.readConcern
  { level: 'majority' }

默认读关注是 local,集群可通过 setDefaultRWConcern 修改,单次读可用 .readConcern() 覆盖。

分步演示

读关注
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。3 个 mongod 组成的副本集;未确认窗口通过 kill -9 两个 secondary 构造(约 10 秒内要完成读写)
    • 没有验证:majority 读在 secondary 上的延迟(需要等 commit point 前进);读关注 available 在分片集群上返回孤儿文档的行为;afterClusterTime 配合因果一致性(下一篇);回滚后 local 读到的数据被撤销(后面 rollback 一篇);snapshot 的 atClusterTime 参数
    京ICP备2026067502号-1京公网安备11010502063487号