跨分片事务在网络抖动下必然悬挂,因mongos需等待所有分片完成两阶段提交,任一分片失联即卡在awaitingResponse状态,已写入分片不自动回滚,仅超时丢弃会话;根本解法是确保事务严格命中单一分片,即查询条件含分片键完整前缀且禁用路由不确定操作。

跨分片事务在网络抖动下必然出现悬挂,这不是配置问题,而是架构硬伤——mongos 无法在部分分片失联时完成两阶段提交,已写入的分片不会自动回滚,事务卡在 awaitingResponse 状态,最终超时但不清理中间态。
为什么网络抖动会让跨分片事务“挂住”而不是失败
mongos 在跨分片事务中扮演协调者角色,必须等待所有目标分片返回 prepare 和 commit 响应。一旦某分片因抖动延迟或丢包未响应,协调流程就阻塞在 awaitingResponse 阶段;此时其他分片可能已完成本地写入,但无法撤回。
-
transactionLifetimeLimitSeconds(默认 60 秒)只控制事务总存活时间,不中断协调等待 -
maxTimeMS对协调阶段无效,仅作用于单分片查询 - 客户端连接断开后,mongos 不会主动 abort 已开始的事务,只丢弃会话上下文
- 恢复后查
currentOp()会看到大量"secs_running": 58+的事务,状态为"inProgress"或"awaitingResponse"
如何快速识别和清理已悬挂的跨分片事务
不能等它超时,得主动干预。重点不是“修复”,而是“止损+对账”。
- 用
db.currentOp({ "secs_running": { "$gt": 30 }, "active": true })找出运行超 30 秒的事务,过滤"lsid"和"txnNumber" - 对每个可疑
lsid,执行db.killOp(<opid>)</opid>—— 注意:这只能终止 mongos 协调上下文,**不回滚已提交的分片写入** - 必须配套执行业务级对账:比如查订单是否创建成功、库存是否扣减、优惠券是否冻结,再人工或脚本触发补偿
- 禁用
sh.stopBalancer()期间的事务清理,否则 chunk 迁移可能干扰 oplog 解析
根本解法:让事务根本不跨分片
所有所谓“优化抖动容忍度”的尝试都无效。唯一可靠路径是消除跨分片事务存在的可能性。
- 事务内所有
find、updateOne、deleteOne操作,查询条件必须含分片键**完整前缀**,例如分片键是{orgId: 1, userId: 1},则{orgId: "abc"}合法,{userId: "xyz"}不合法 - 禁用
$or、$in(多值)、$regex等导致路由不确定的操作,mongos 会广播到所有分片 - 写操作必须显式带分片键字段,否则直接报错
Location40587 - 在应用层做路由预检:事务开启前,先用
db.runCommand({ getShardVersion: 1 })确认当前 mongos 路由表新鲜,避免 stale config 导致误路由
跨分片事务的悬挂风险不在代码逻辑里,而在分片键设计和查询模式里。一旦允许 createdAt 或 status 单独作为查询条件,就等于把事务命运交给网络抖动——这不是容错问题,是架构越界。











