事务连接失败主因是ReadPreference非Primary或authSource未显式指定,导致二次认证时默认使用连接路径末尾数据库而非真实用户库(如admin),引发Authentication failed(code 18)。

事务连接失败,90%是因为 ReadPreference 和认证数据库不匹配,而不是账号密码错了。
为什么开启事务后突然 Authentication failed?
MongoDB 事务要求所有参与节点必须使用 primary 读偏好(ReadPreference=Primary),且客户端必须能通过认证数据库(如 admin)完成初始握手。一旦配置了 ReadPreference=Secondary 或未显式指定认证源(authSource),事务启动时会触发二次认证——而这次认证默认 fallback 到连接字符串末尾的数据库名(比如 /myappdb),而非真实创建用户的 admin 库,直接报错 Authentication failed (code 18)。
-
ReadPreference不是“可选优化项”,而是事务的强制前提;设成Secondary或Nearest会导致驱动在事务内部尝试向从节点发 auth 请求,必然失败 - 连接字符串末尾的数据库名(如
mongodb://u:p@h:27017/myappdb)**不等于**认证数据库;它只是默认操作库,authSource才决定去哪个库查用户 - .NET 驱动(如 MongoDB.Driver 2.20+)在启用事务时会自动切换到
Primary模式,但前提是初始连接已通过authSource正确完成认证
连接字符串里必须显式写死 authSource
即使你的用户建在 admin 库,也不能依赖“默认行为”。.NET 客户端解析连接字符串时,若没看到 authSource 参数,可能用连接路径末尾的数据库名做认证源(尤其在带 replica set 的场景下更不稳定)。
- 错误写法:
mongodb://root:pass@localhost:27017/myappdb→ 认证时会尝试在myappdb库查root用户,失败 - 正确写法:
mongodb://root:pass@localhost:27017/myappdb?authSource=admin→ 明确告诉驱动:去admin库验证 - 如果用户建在其他库(如
test),则必须写authSource=test,不能只改连接路径末尾的库名 - 密码含特殊字符(如
@、/、:)必须 URL 编码,否则解析中断;例如密码p@ss/w0rd要写成p%40ss%2Fw0rd
.NET 代码中禁用自动 ReadPreference 覆盖
某些旧版驱动或自定义设置会覆盖事务所需的 ReadPreference。不要在 MongoClientSettings 里手动设 ReadPreference,让事务上下文自己控制。
- ❌ 错误:在
MongoClientSettings中硬编码ReadPreference = ReadPreference.Secondary→ 事务启动即崩 - ✅ 正确:完全不设
ReadPreference,或显式设为ReadPreference.Primary(推荐) - 事务内执行操作时,用
session.WithTransaction(),不要在 session 外调用collection.Find()等可能触发非 primary 读的操作 - 检查是否启用了
MaxConnectionLifeTime或MinConnectionPoolSize过小,导致连接复用时携带了过期/错配的认证上下文
最容易被忽略的点:副本集里的 admin 库不是共享的
在 replica set 部署中,admin 库的用户元数据只存在于主节点(primary)上。如果你的应用连接字符串指向的是 secondary 节点(比如用了 readPreference=secondary),即使 authSource=admin 写对了,首次握手也会失败——因为 secondary 不负责认证。
- 必须确保连接字符串指向 primary,或使用完整的 replica set URI:
mongodb://node1:27017,node2:27017,node3:27017/myappdb?replicaSet=myrs&authSource=admin - 云服务(如 MongoDB Atlas、阿里云 MongoDB)的连接地址默认已处理 replica set 发现逻辑,但务必确认控制台显示的连接串包含
&authSource=admin且没有多余空格 - 本地测试时,用
mongosh --host localhost:27017 --eval "db.runCommand({isMaster:1})"确认当前节点角色是"ismaster": true


















