网络断开后重试一次 $inc 会不会加两次?可重试写入是怎么避免重复执行的?这篇用 failpoint 制造连接断开,再手写 OP_MSG 把同一个请求发两次,看服务端的去重。
update_one 的结果:matched 1 modified 1;n = 1;驱动发出的 update 命令(命令名, txnNumber, 带 lsid): [('update', 2, True), ('update', 2, True)]
(两次 update 的 txnNumber 相同——重试带着同一个事务号,服务端据此判断是不是重复请求)连接中断时驱动自动重发写命令,带着同一个 lsid 和 txnNumber;retryWrites=False 则直接抛错。
AutoReconnect: 127.0.0.1:9901: connection closed (configured timeouts: connectTimeoutMS: 20000. n = 1(这次故障发生在执行之前,没有加)
同一个 lsid + txnNumber 的重发,服务端不再执行,而是返回之前的结果并带上 retriedStmtIds。
第 1 次(txnNumber 100) → n=1 nModified=1 retriedStmtIds=None;库里 n = 1 重发同一个请求(txnNumber 100) → n=1 nModified=1 retriedStmtIds=[0];库里 n = 1 新请求(txnNumber 101) → n=1 nModified=1 retriedStmtIds=None;库里 n = 2
靠 oplog 条目里的 lsid、txnNumber、stmtId 和 config.transactions 里每个会话最后的事务号。
oplog: op=u txnNumber=100 stmtId=0 o2={'_id': 1} o={'$v': 2, 'diff': {'u': {'n': 1}}}
oplog: op=u txnNumber=101 stmtId=0 o2={'_id': 1} o={'$v': 2, 'diff': {'u': {'n': 2}}}
config.transactions 里该会话的记录: txnNum=101,lastWriteOpTime=Timestamp(1791776225, 4)批量 insert 的每条语句有自己的 stmtId,整批重发只会执行一次。
第 1 次:n=3;重发:n=3 retriedStmtIds=[0, 1, 2];集合里文档数 3(只有 3 个,没有重复键错误)
multi 的 update 和 delete 不可重试,遇到连接断开直接报错。
update_many 遇到同样的连接断开:不重试,直接抛 AutoReconnect;发出的 update 命令 [('update', None, True)]
delete_one:被重试后成功;发出的 delete 命令 [('delete', 1, True), ('delete', 1, True)]
独立实例(非副本集)上 retryWrites=True 的 insert:成功(驱动检测到不支持就自动不用)
# 直接带 txnNumber 的命令在独立实例上被拒绝
test> try { db.runCommand({insert: "t", documents: [{x: 2}], lsid: {id: UUID()}, txnNumber: Long(1)}) } catch (e) { e.message }
Transaction numbers are only allowed on a replica set member or mongos事务号只允许副本集或 mongos,独立实例上驱动会自动关闭重试写。