Go连MongoDB必须Ping并用带超时的context,bson标签需严格匹配字段名(如_id),primitive.ObjectID须显式赋值并用Hex()转字符串,InsertOne返回的InsertedID需类型断言,Find返回的cursor必须Close以防连接泄漏。

Go 连 MongoDB 不是“配好驱动就能跑”,真正卡住人的往往是 context 超时、bson 标签错位、primitive.ObjectID 用法不一致,以及连接池没关导致资源泄漏。这些不是语法错误,而是运行时才暴露的隐性坑。
mongo.Connect 后必须 Ping,且不能用 context.TODO()
很多人直接拿示例里的 context.TODO() 去连生产环境,结果服务启动后看似成功,但第一次查询就超时或 panic。原因在于 mongo.Connect 是异步建立连接,返回 client 时底层可能还没 ready。
-
Ping必须带一个有明确 deadline 的context,比如context.WithTimeout(context.Background(), 5*time.Second) - 别复用
context.TODO()—— 它不带取消机制,容易让 goroutine 持久挂起 - 如果
Ping失败,err可能是"context deadline exceeded"或"no reachable servers",说明网络或配置问题,不是代码写错了
bson struct tag 写错会导致字段完全丢失或写入 nil
Go 结构体映射到 BSON 文档时,bson tag 决定字段名和行为。漏写、拼错、大小写不一致,都会让字段静默消失 —— 插入时没存,查询时取不到,还不会报错。
-
ID primitive.ObjectID `bson:"_id,omitempty"`中的_id必须全小写加下划线,写成ID或id都无效 -
omitempty表示值为空时不序列化,但primitive.ObjectID默认非零值,所以新建文档时必须显式赋值primitive.NewObjectID(),否则插入后_id是空对象 - 嵌套结构(如
Address)也要加bsontag,否则整个字段被忽略,不是只丢子字段
InsertOne 返回的 InsertedID 是 interface{},不是 string
新手常把 result.InsertedID 直接当字符串打印或传给 HTTP 响应,结果看到 ObjectIdHex("...") 或 panic:interface conversion: interface {} is primitive.ObjectID, not string。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法是断言类型:
id := result.InsertedID.(primitive.ObjectID) - 转成字符串用
id.Hex(),不是fmt.Sprintf("%s", id)—— 后者会输出调试格式 - 如果不确定类型(比如批量插入后遍历
InsertedIDs),先用switch v := result.InsertedID.(type)分支处理
Find 返回的 cursor 必须 Close,否则连接泄漏
几乎所有 Find 或 Aggregate 操作都返回 *mongo.Cursor,它背后持有连接资源。忘了 defer cursor.Close(ctx),短时间看不出问题,压测或长周期运行后连接数暴涨,MongoDB 报 "too many connections"。
- 哪怕只取一条(
FindOne),返回的*mongo.SingleResult也建议调用Decode后立即释放,虽然它内部做了 cleanup,但显式 close 更稳妥 - 循环遍历 cursor 时,
Next失败(如网络中断)也要Close,不能只在成功路径里 close - 常见反模式:
for cursor.Next(ctx) { ... }末尾没 close,或 defer 放在 for 外层导致只 close 最后一次迭代的 cursor
最易被忽略的是:BSON 字段名区分大小写,且 $ 开头的操作符(如 $set、$push)必须在 update filter 里用 bson.D 或 bson.M 显式构造,不能靠结构体 tag 自动推导 —— 这类逻辑不在 struct 层,而在 query 构建层,错了一点就查不到或改错数据。


















