Go操作MongoDB关键在于正确使用context超时和bson标签:InsertOne需带超时ctx并确保结构体字段导出且有bson tag,Find必须用bson.M构造filter、ObjectId需ObjectIDHex解析,UpdateOne须含$操作符,client应全局复用而非每次新建。

Go 直接操作 MongoDB 存文档,关键不是“能不能”,而是 mongo-go-driver 的 InsertOne 和 Find 怎么用才不踩坑——比如结构体字段没加 json 或 bson tag 导致写入空对象,或者忘记传 context.Context 让操作卡死。
InsertOne 写入失败但没报错?检查结构体 tag 和 context
常见现象:调用 collection.InsertOne(ctx, data) 返回 <nil>,但数据库里啥都没写进去。这不是成功,是 ctx 被 cancel 或超时了,错误被吞了。
-
ctx必须带超时,别直接用context.Background(),尤其在 HTTP handler 里要用ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) - 结构体字段必须有
bsontag,否则 driver 默认忽略所有字段:type User struct { Name string `bson:"name"` Age int `bson:"age"` } - 如果结构体字段首字母小写(未导出),即使写了 tag 也写不进去——Go 的反射拿不到私有字段
Find 查询返回空结果?确认 filter 格式和 ObjectId 解析
查不到数据,大概率是 filter 写错了。MongoDB 的 _id 是 ObjectID 类型,不能当字符串直接比对。
- 查指定
_id时,先用primitive.ObjectIDHex("...")转换字符串,再构造 filter:filter := bson.M{"_id": primitive.ObjectIDHex(idStr)} - 查字符串字段用
bson.M{"name": "Alice"}没问题,但注意大小写敏感,且不会自动 trim 空格 - 用
Find后必须调用cursor.Next(ctx)才能取值,直接cursor.All(ctx, &results)更安全,但要注意内存占用
UpdateOne 更新不生效?别漏掉 $set 和匹配条件的严格性
调用 UpdateOne 后文档没变,通常是因为 update document 缺少操作符,或匹配条件太松/太紧。
立即学习“go语言免费学习笔记(深入)”;
- update 参数必须是带操作符的对象,比如
bson.M{"$set": bson.M{"age": 30}},直接传bson.M{"age": 30}会替换整个文档(等价于$replaceRoot) - 更新前先用
FindOne验证 filter 是否真能命中目标文档,避免“以为更新了,其实没找到” - 如果字段可能为空,
$set不会删字段;想清空字段得用$unset,或者显式设为nil(需对应字段类型支持)
连接池和 client 复用:别在 handler 里 new client
每次 HTTP 请求都新建 mongo.Connect(),不出几分钟就耗尽文件描述符,报错 dial tcp: lookup ...: no such host 或连接超时。
-
mongo.Connect()是重量级操作,应在应用启动时调用一次,全局复用返回的*mongo.Client - 用
defer client.Disconnect(ctx)放在 main 函数退出前,别放在每个 handler 里 - 集合(
*mongo.Collection)可以按需获取:client.Database("test").Collection("users"),这个开销很小,不用缓存
最常被跳过的其实是 context 生命周期和 bson tag 的组合影响——一个没设 timeout,一个字段漏 tag,两个小疏忽就能让服务看起来“正常运行”,实际一条数据都存不进库。


















