
在使用mgo驱动操作mongodb时,若为结构体错误地实现了调用自身递归反序列化的`setbson`方法,将导致无限递归并触发栈溢出(stack overflow)——这是典型的接口实现陷阱。
当你为 Session 类型显式实现 SetBSON 方法,并在其中直接调用 raw.Unmarshal(rcv) 时,就无意中触发了 mgo 的递归调用链:
- Find().One(&session) 尝试将 BSON 数据反序列化为 *Session;
- 因 Session 实现了 bson.Setter 接口,mgo 会优先调用其 SetBSON 方法;
- 而该方法又调用 raw.Unmarshal(rcv) —— 这一操作再次尝试将原始 BSON 解析为 *Session,从而再次进入 SetBSON;
- 如此循环,永不终止,最终耗尽栈空间,panic 报错 runtime: goroutine stack exceeds 1000000000-byte limit。
✅ 正确做法是:仅在需要自定义反序列逻辑时才实现 SetBSON,且绝不可在其中递归调用 Unmarshal 到同一类型。对于绝大多数场景(如本例),完全无需实现 SetBSON —— mgo 默认的结构体字段映射已足够健壮。
以下是修复后的精简示例(移除危险的 SetBSON 实现):
package main
import (
"fmt"
"os"
"gopkg.in/mgo.v2" // 注意:推荐使用新导入路径(原 labix.org/v2/mgo 已弃用)
"gopkg.in/mgo.v2/bson"
)
type Session struct {
Id bson.ObjectId `bson:"_id"`
Data map[string]interface{} `bson:"data"`
}
type Authen struct {
Name string `bson:"name"`
Email string `bson:"email"`
}
func main() {
uri := "mongodb://localhost:27017"
sess, err := mgo.Dial(uri)
if err != nil {
fmt.Printf("Can't connect to MongoDB: %v\n", err)
os.Exit(1)
}
defer sess.Close()
collection := sess.DB("test").C("sess")
a := &Authen{Name: "Cormier", Email: "cormier@example.com"}
s := &Session{
Id: bson.NewObjectId(),
Data: map[string]interface{}{"logged": a},
}
if err = collection.Insert(s); err != nil {
fmt.Printf("Insert failed: %v\n", err)
os.Exit(1)
}
var result Session
if err = collection.Find(bson.M{}).One(&result); err != nil {
fmt.Printf("Query failed: %v\n", err)
os.Exit(1)
}
fmt.Printf("Retrieved: %+v\n", result)
}⚠️ 补充注意事项:
- labix.org/v2/mgo 已停止维护,强烈建议迁移到社区活跃分支,如 github.com/globalsign/mgo 或更现代的官方驱动 go.mongodb.org/mongo-driver/mongo;
- 若确需自定义反序列化(例如处理嵌套时间戳、加密字段等),SetBSON 内应手动解析 raw.Data 或使用 bson.Unmarshal() 到临时结构体/基础类型,再赋值给接收者字段,避免闭环调用;
- 始终启用 go vet 和静态检查工具,部分递归实现问题可在编译期被识别。
总结:接口实现需敬畏调用契约。Setter 不是“让我自己解自己”,而是“让我控制如何被解”——控制权在你,但递归出口必须由你明确切断。











