操作场景
Go 语言以其高并发性能和编译执行效率,广泛应用于后端微服务和基础设施开发。MongoDB 官方 Go Driver 提供了类型安全的数据库操作 API,与 Go 的并发模型良好配合。本文提供使用 Go 官方驱动连接云数据库 MongoDB 的完整示例。
前提条件
已创建云数据库 MongoDB 实例,且实例状态为运行中。
已准备一台与 MongoDB 实例在同一 VPC 内的云服务器 CVM,且已安装 Go 1.18 或以上版本。
安装驱动
说明:
早期社区驱动
mgo(gopkg.in/mgo.v2)已停止维护,请使用 MongoDB 官方 Go Driver。go get go.mongodb.org/mongo-driver/mongo
go.mod 示例:
module mongodb-demogo 1.21require go.mongodb.org/mongo-driver v1.13.1
注意事项
Context 使用:Go 驱动的所有操作都接受
context.Context 参数,用于控制超时和取消。生产环境中应为每个操作设置合理的超时时间。密码编码:使用
url.QueryEscape() 对密码进行 URL 编码,避免特殊字符导致连接串解析错误。结构体映射:通过
bson 标签将 Go 结构体字段映射到 MongoDB 文档字段。字段名默认采用小写,建议显式指定 bson 标签。连接复用:
mongo.Client 内置连接池,应在应用中全局复用,通常作为依赖注入到各处理函数。Cursor 关闭:使用
Find() 返回的 Cursor 在使用完毕后必须调用 Close(),推荐使用 defer 语句。连接和 CRUD 操作示例
package mainimport ("context""fmt""log""net/url""time""go.mongodb.org/mongo-driver/bson""go.mongodb.org/mongo-driver/bson/primitive""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options""go.mongodb.org/mongo-driver/mongo/readpref")// User 定义用户文档结构type User struct {ID primitive.ObjectID `bson:"_id,omitempty"`Username string `bson:"username"`Age int `bson:"age"`Email string `bson:"email"`CreatedAt time.Time `bson:"created_at"`}func main() {// 连接 URI(请替换为您的实际连接信息)// 如密码包含特殊字符,使用 url.QueryEscape() 进行编码user := "mongouser"password := url.QueryEscape("thepasswordA1")host := "10.66.187.127:27017"uri := fmt.Sprintf("mongodb://%s:%s@%s/admin", user, password, host)// 副本集连接串(推荐):// uri := fmt.Sprintf("mongodb://%s:%s@IP1:27017,IP2:27017,IP3:27017/admin?replicaSet=cmgo-xxxxxxxx_0", user, password)// 配置连接参数clientOpts := options.Client().ApplyURI(uri).SetMaxPoolSize(50). // 连接池上限SetMinPoolSize(5). // 连接池下限SetConnectTimeout(10 * time.Second). // 连接超时SetSocketTimeout(30 * time.Second). // 套接字超时SetMaxConnIdleTime(60 * time.Second). // 空闲连接超时SetRetryWrites(true). // 启用可重试写入SetRetryReads(true) // 启用可重试读取// 建立连接ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()client, err := mongo.Connect(ctx, clientOpts)if err != nil {log.Fatalf("连接失败: %v", err)}defer func() {if err := client.Disconnect(context.Background()); err != nil {log.Printf("断开连接失败: %v", err)}fmt.Println("连接已关闭")}()// 验证连接if err := client.Ping(ctx, readpref.Primary()); err != nil {log.Fatalf("Ping 失败: %v", err)}fmt.Println("连接成功")// 选择数据库和集合collection := client.Database("mydb").Collection("users")// ========== 插入文档 ==========user1 := User{Username: "jack",Age: 31,Email: "jack@example.com",CreatedAt: time.Now(),}insertResult, err := collection.InsertOne(context.Background(), user1)if err != nil {log.Fatalf("插入失败: %v", err)}fmt.Printf("插入文档 ID: %v\\n", insertResult.InsertedID)// 批量插入users := []interface{}{User{Username: "alice", Age: 28, Email: "alice@example.com", CreatedAt: time.Now()},User{Username: "bob", Age: 35, Email: "bob@example.com", CreatedAt: time.Now()},}insertManyResult, err := collection.InsertMany(context.Background(), users)if err != nil {log.Fatalf("批量插入失败: %v", err)}fmt.Printf("批量插入 %d 条文档\\n", len(insertManyResult.InsertedIDs))// ========== 查询文档 ==========var foundUser Usererr = collection.FindOne(context.Background(), bson.M{"username": "jack"}).Decode(&foundUser)if err != nil {log.Fatalf("查询失败: %v", err)}fmt.Printf("查询单条: %+v\\n", foundUser)// 条件查询(年龄大于 30)cursor, err := collection.Find(context.Background(), bson.M{"age": bson.M{"$gt": 30}})if err != nil {log.Fatalf("条件查询失败: %v", err)}defer cursor.Close(context.Background())fmt.Println("年龄大于 30 的用户:")for cursor.Next(context.Background()) {var u Userif err := cursor.Decode(&u); err != nil {log.Printf("解码失败: %v", err)continue}fmt.Printf(" - %s, 年龄: %d\\n", u.Username, u.Age)}// ========== 更新文档 ==========updateResult, err := collection.UpdateOne(context.Background(),bson.M{"username": "jack"},bson.M{"$set": bson.M{"age": 32}},)if err != nil {log.Fatalf("更新失败: %v", err)}fmt.Printf("更新 %d 条文档\\n", updateResult.ModifiedCount)// ========== 删除文档 ==========deleteResult, err := collection.DeleteMany(context.Background(),bson.M{"username": bson.M{"$in": bson.A{"jack", "alice", "bob"}}},)if err != nil {log.Fatalf("删除失败: %v", err)}fmt.Printf("删除 %d 条文档\\n", deleteResult.DeletedCount)}
运行输出示例:
连接成功插入文档 ID: ObjectID("6789abcdef1234567890abcd")批量插入 2 条文档查询单条: {ID:ObjectID("6789abcdef1234567890abcd") Username:jack Age:31 Email:jack@example.com CreatedAt:2026-04-17 15:00:00 +0800 CST}年龄大于 30 的用户:- jack, 年龄: 31- bob, 年龄: 35更新 1 条文档删除 3 条文档连接已关闭
常见问题
连接时提示 "server selection error: server selection timeout"
错误信息示例:
server selection error: context deadline exceeded, current topology: { Type: Unknown, Servers: [{ Addr: 10.66.187.127:27017, Type: Unknown, Last error: connection() error occurred during connection handshake: auth error: sasl conversation error: unable to authenticate using mechanism "SCRAM-SHA-1" }, ] }
请按以下步骤排查:
1. 确认 URI 中指定了认证库
admin。// 正确:URI 路径为 /adminuri := "mongodb://mongouser:thepasswordA1@10.66.187.127:27017/admin"// 正确:连接业务库时通过 authSource 指定认证库uri = "mongodb://mongouser:thepasswordA1@10.66.187.127:27017/mydb?authSource=admin"// 错误:连接业务库时未指定 authSource,驱动将默认以 mydb 库认证,导致认证失败uri = "mongodb://mongouser:thepasswordA1@10.66.187.127:27017/mydb"
2. 确认密码中的特殊字符已进行 URL 编码:使用
url.QueryEscape() 对密码进行编码:import "net/url"password := url.QueryEscape("pass@123") // 输出: pass%40123uri := fmt.Sprintf("mongodb://mongouser:%s@10.66.187.127:27017/admin", password)
3. 确认网络连通性:在 CVM 上执行以下命令,验证网络是否可达:
telnet <MongoDB实例IP> 27017
输出
Connected 表示网络可达,请检查认证配置。输出
Connection timed out 表示网络不通,请确认 CVM 与 MongoDB 实例在同一 VPC 内,并检查安全组规则。运行时提示 "context deadline exceeded"
错误信息示例:
context deadline exceeded
该错误表示操作超时。根据出现的场景不同,排查方式有所区别:
场景一:连接阶段超时。
检查
SetConnectTimeout 配置是否合理。内网环境下,10秒足够:clientOpts := options.Client().ApplyURI(uri).SetConnectTimeout(10 * time.Second)
场景二:查询或写入阶段超时。
检查传入操作的
context 超时是否过短:// 错误:超时过短,复杂查询可能无法在 1 秒内完成ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)// 正确:根据业务复杂度设置合理的超时时间ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()cursor, err := collection.Find(ctx, bson.M{"age": bson.M{"$gt": 30}})
场景三:长时间运行后出现超时。
可能是空闲连接被服务端断开。请确认以下配置:
clientOpts := options.Client().ApplyURI(uri).SetMaxConnIdleTime(60 * time.Second). // 空闲连接 60 秒后关闭SetRetryWrites(true). // 开启可重试写入SetRetryReads(true) // 开启可重试读取
编译时提示 "cannot find module providing package go.mongodb.org/mongo-driver/..."
错误信息示例:
cannot find module providing package go.mongodb.org/mongo-driver/mongo
请按以下步骤排查:
1. 确认 Go 模块已初始化:在项目目录下确认存在
go.mod 文件。如不存在,请执行:go mod init <module-name>
2. 安装 MongoDB Go Driver。
go get go.mongodb.org/mongo-driver/mongo
3. 执行
go mod tidy 整理依赖。go mod tidy
安装完成后,确认
go.mod 文件中包含如下依赖:require go.mongodb.org/mongo-driver v1.13.1
使用已停维的 mgo 驱动是否可行?
社区驱动
mgo(gopkg.in/mgo.v2)已于2018年停止维护,存在以下限制:对比项 | mgo(已停维) | 官方 Go Driver |
维护状态 | 已停止维护 | 活跃维护 |
MongoDB 版本支持 | 最高3.6 | 支持4.0 - 8.0 |
Context 支持 | 不支持 | 支持 |
事务支持 | 不支持 | 支持 |
可重试读写 | 不支持 | 支持 |