w: "majority"要求写操作被多数可投票且可通信节点确认并完成journal持久化(若j:true),否则事务提交时直接报writeconcernmajoritynotsatisfied错误。

w: "majority" 不是“多数节点在线”就自动满足的,它要求写操作必须被复制到「有投票权(votes > 0)且当前可通信」的大多数节点,并完成 journal 持久化(若设了 j: true)。事务提交时若不满足,会直接抛出 WriteConcernMajorityNotSatisfied 错误——这不是网络抖动提示,而是副本集已无法提供多数写入保障。
为什么事务里更容易触发 WriteConcernMajorityNotSatisfied
事务的 writeConcern 只在 commitTransaction() 时统一校验,不是每个 insertOne 单独检查。这意味着:
- 事务内所有写操作都“绑定”在同一个多数确认门槛上,只要任意一个从节点失联、journal 写入卡住、或 votes 配置异常,整个事务就失败
- 事务默认不继承客户端全局 writeConcern,必须显式传入
session.startTransaction({ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }),漏设就会用默认w: 1,但事务里又禁止混用,导致报错"Cannot specify write concern in both transaction options and operation" - 分片集群中,事务跨分片时,
w: "majority"是按「每个分片副本集内部」分别计算 majority,不是全集群加总——某个分片的 secondary 全挂,该分片就无法满足 majority
检查从库健康度不能只看 rs.status() 的 stateStr
rs.status() 显示 "stateStr" : "SECONDARY" 只代表节点进程活着、能通信,不等于它能参与 majority 投票或接收 oplog。重点查以下字段:
-
members[n].health:必须为1,0表示完全不可达(如防火墙拦截、mongod 崩溃) -
members[n].state:值为2(SECONDARY)才有效;8(DOWN)或10(ROLLBACK)直接排除在 majority 外 -
members[n].votes:必须为1;设为0的隐藏/延迟节点不计入 majority 计算,但可能让实际 majority 数变小(比如 5 节点中 2 个votes: 0,则 majority = ⌊(3+1)/2⌋ = 2,而非 ⌊(5+1)/2⌋ = 3) -
members[n].lastHeartbeatRecv:距今超过 10 秒说明心跳异常,该节点大概率已被主节点踢出同步源列表
如何快速定位哪个节点拖垮 majority
运行这个聚合查询(在 primary 上执行),它会列出所有影响 majority 判定的关键状态:
db.adminCommand({
"replSetGetStatus": 1
}).members.filter(m => m.state !== 1).map(m => ({
name: m.name,
state: m.stateStr,
health: m.health,
votes: m.votes,
lastHeartbeatRecv: m.lastHeartbeatRecv,
optimeDate: m.optimeDate
}))
重点关注返回中 health: 0 或 stateStr: "UNKNOWN" 或 lastHeartbeatRecv 时间过老的条目。另外,检查 members[n].optimeDate 是否严重落后于 primary(差 > 30 秒说明同步延迟过大,journal 写入可能卡住)。
修复后仍报错?检查 journal 和 wtimeout 配合是否生效
即使所有从库都健康,WriteConcernMajorityNotSatisfied 仍可能出现,原因常被忽略:
-
j: true生效的前提是服务端storage.journal.enabled: true(4.0+ 默认开启,但单机测试或旧部署可能关掉);用db.adminCommand({ getCmdLineOpts: 1 })查storage.journal.enabled确认 -
wtimeout必须设为合理值(如5000),否则网络分区时事务会无限等待;注意它只对w: "majority"生效,w: 1下无效 - 事务里不能在单个操作里再设 writeConcern,比如
coll.insertOne({...}, { writeConcern: {...} })会直接报错,必须只在startTransaction()里指定一次
真正麻烦的是:多数节点健康 + journal 开启 + wtimeout 设置正确,但某节点磁盘满、I/O 阻塞或 journal commit 周期被拉长(如 storage.journal.commitIntervalMs: 300),这时它收得到 oplog 却迟迟不刷盘,j: true 就永远等不到确认——这种问题不会出现在 rs.status() 里,得盯 mongod 日志里的 journal 相关 warn/error。











