在 MongoDB 分片集群中,一个跨文档事务并不是由单一 mongos 或单个分片直接完成的。驱动发起的 commit 请求会先到达某个承担协调者角色的 mongos 或分片主节点,该节点负责收集所有参与分片的事务参与者的投票,再决定提交或中止。当这个协调者因为选举、降级、计划内维护或网络抖动而失去角色时,整个事务可能无法继续推进,客户端就会收到错误码 1810。

这个错误全称为 TransactionCoordinatorSteppingDown,字面意思是事务协调器正在让出主节点身份。它和普通的写入冲突、锁等待、重复键错误都不同,1810 的本质是分布式事务的协调角色发生了变化,而不是数据本身违反了约束。理解这一点很重要,否则排查方向很容易偏向业务数据校验,浪费大量时间。
错误码1810的产生链路
MongoDB 从 4.2 版本开始支持跨分片多文档事务,事务的提交过程可以简化成两阶段提交。第一阶段协调者向所有参与分片发送 prepare 请求,各参与分片把事务的写操作固化到本地事务记录中并返回准备结果。第二阶段协调者根据所有参与者的反馈决定提交或中止,再通知各分片执行最终决策。协调者就是在这两个阶段中间最容易出现 1810 的地方。
如果协调者所在分片的主节点在 prepare 之后、commit/abort 决策之前发生主从切换,新的主节点可能缺少该事务的完整协调状态。旧协调者会让出角色,客户端收到的就是 1810。常见触发因素包括:分片主节点发生计划内重启、内存或磁盘压力导致选举触发、网络分区使主节点失去多数派连接、以及管理员手动执行 rs.stepDown 等操作。除此之外,协调者等待参与分片回复的时间超出 internalTransactionCommitTimeout 也可能增加失败概率,因为超时本身不等于 1810,但会让事务状态更脆弱。
还有一种容易忽略的情况是 mongos 作为协调者的情况。在部分部署架构里,如果 mongos 与分片主节点之间发生连接中断,即使分片主节点没有切换,驱动也可能收到 1810。原因是 mongos 无法确认协调状态是否已经安全落盘,只能抛出这个错误让客户端重试。
从日志和指标验证协调者故障
遇到 1810 后,第一步不是马上改代码,而是确认故障发生的位置。登录到对应分片的 mongod 日志,在错误发生时间附近搜索 transaction coordinator 或 TransactionCoordinator 相关记录。通常会看到类似 Coordinating commit for transaction ... stepped down 这样的信息。注意日志中会带出事务的 lsid 和 txnNumber,这两个字段能帮你把客户端报错与具体事务对应起来。
除了日志,还可以使用 serverStatus 命令观察事务相关指标。在 mongosh 中执行 db.serverStatus().transactions 会返回当前事务数量、提交与中止总数等数据。重点关注 currentActive 是否在错误发生时出现陡增,以及 commitTypes 中 noShards 和 twoPhaseCommit 的分布。如果 twoPhaseCommit 提交比例高,而错误日志里又频繁出现 coordinator stepping down,基本可以确定是分片主节点不稳定导致。
另一个有用的命令是 db.currentOp(),在事务执行期间可以查看事务的协调状态。尝试在提交前执行该命令,过滤 active 为 true 的操作,观察 transaction 字段中 coordinator 与 participants 的分布。如果某个参与分片长时间没有响应,或者协调者反复切换,这些信息会直接指向网络或节点健康问题。
// 在 mongosh 中查看事务相关指标
db.serverStatus().transactions
// 示例输出片段
{
currentActive: 0,
currentInactive: 0,
currentOpen: 0,
totalAborted: 43,
totalCommitted: 120,
totalStarted: 163
}
// 查看正在执行的事务操作
db.currentOp({ "transaction.parameters.txnNumber": { $exists: true } })
实际生产环境中,不建议每次事务都执行 db.currentOp(),这会产生额外开销。更好的做法是在监控系统中采集 serverStatus 指标,当 1810 错误率上升时,再登录节点执行诊断命令。这种按需排查的方式对线上性能影响最小。
驱动级重试与事务边界设计
1810 属于可重试错误,但重试方式不能简单地包一层 while 循环。MongoDB 官方驱动在事务提交失败时错误对象会携带 code 属性,只有在错误码为 1810、251 或部分网络错误时,才应该使用新的会话和事务号重试。如果是其他错误码,例如 11000 重复键、121 文档校验失败,重试只会重复失败。
下面的 Python 示例演示了如何安全地处理 1810。注意每次重试都要使用全新的事务,不能用旧会话重开事务,因为旧会话的 txnNumber 可能已经处于不确定状态。代码里通过 start_session 创建新会话,而不是复用第一次失败时的会话。
from pymongo import MongoClient
from pymongo.errors import OperationFailure, PyMongoError
import time
client = MongoClient("mongodb://127.0.0.1:27017/?replicaSet=rs0")
def run_transaction_with_retry(txn_func, max_retries=3):
last_exc = None
for attempt in range(1, max_retries + 1):
session = client.start_session()
try:
with session.start_transaction():
result = txn_func(session)
return result
except OperationFailure as exc:
last_exc = exc
if exc.code == 1810:
print(f"Transaction coordinator stepped down, retry {attempt}")
time.sleep(attempt * 0.5)
continue
raise
except PyMongoError as exc:
last_exc = exc
raise
finally:
session.end_session()
raise last_exc
def transfer(session):
collection = client.test.accounts
collection.update_one(
{"account": "A"},
{"$inc": {"balance": -100}},
session=session
)
collection.update_one(
{"account": "B"},
{"$inc": {"balance": 100}},
session=session
)
run_transaction_with_retry(transfer)
代码中的 retry 次数不宜设置过大。事务本身会占用参与分片的资源,无限重试可能放大故障。建议结合指数退避和最大重试次数,例如三次重试后直接向上层返回失败,由业务侧决定是否进入人工对账流程。另外,把事务边界尽量缩小,不要在事务内部执行耗时查询、外部 HTTP 调用或大量文档扫描,这样可以减少协调者窗口期被主从切换命中的概率。
从架构配置上降低1810发生概率
如果 1810 在集群里频繁出现,根本原因通常是分片主节点不稳定。优先检查各分片的复制集健康状态,确保没有节点因为磁盘满、OOM、CPU 长时间打满而反复触发选举。使用 db.serverStatus().repl 观察主节点的 term 和 lastElectionInfo,如果 term 频繁增长,说明选举次数异常。
网络层也要排查。协调者与参与分片之间建议保持低延迟、稳定连接。可以适当调整 MongoDB 参数 internalTransactionCommitTimeout 或 transactionLifetimeLimitSeconds,但这些参数只能提供缓冲,不能解决节点反复切换的问题。生产环境中更推荐为分片主节点预留足够的 CPU 和内存,避免同一台机器承载过多高负载分片,导致主节点在事务提交阶段发生无计划切换。
如果业务允许,建议在跨分片事务场景中尽量使用较新的 MongoDB 版本。事务协调器的故障恢复逻辑在 4.4、5.0、6.0 中都有改进,新版本对协调者状态持久化和参与分片超时处理更加完善。当然,升级本身不能充当唯一手段,合理的部署架构、监控告警以及驱动重试策略,才是应对 1810 的完整方案。
MongoDB故障码1810事务协调器TransactionCoordinatorSteppingDown修改时间:2026-09-23 14:44:11