Go语言MongoDB文档数据库操作指南
Go语言MongoDB文档数据库操作指南
引言
MongoDB是最流行的NoSQL文档数据库之一,以其灵活的数据模型和出色的可扩展性著称。Go语言通过官方驱动mongo-go-driver可以高效地与MongoDB进行交互。本文将深入探讨Go语言中MongoDB的操作技巧和最佳实践。
一、环境配置与连接
1.1 安装依赖
go get go.mongodb.org/mongo-driver/mongo go get go.mongodb.org/mongo-driver/mongo/options1.2 连接配置
package main import ( "context" "fmt" "log" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // 连接选项 clientOptions := options.Client(). ApplyURI("mongodb://localhost:27017"). SetConnectTimeout(10 * time.Second). SetMaxPoolSize(100) // 建立连接 client, err := mongo.Connect(context.TODO(), clientOptions) if err != nil { log.Fatalf("Failed to connect to MongoDB: %v", err) } // 验证连接 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := client.Ping(ctx, nil); err != nil { log.Fatalf("Failed to ping MongoDB: %v", err) } fmt.Println("Successfully connected to MongoDB") }二、数据模型定义
2.1 基础文档结构
import ( "go.mongodb.org/mongo-driver/bson/primitive" "time" ) type User struct { ID primitive.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` Email string `bson:"email"` Age int `bson:"age"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"` } type Product struct { ID primitive.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` Price float64 `bson:"price"` Category string `bson:"category"` Tags []string `bson:"tags"` Description string `bson:"description,omitempty"` Stock int `bson:"stock"` CreatedAt time.Time `bson:"created_at"` }2.2 嵌套文档结构
type Address struct { Street string `bson:"street"` City string `bson:"city"` State string `bson:"state"` ZipCode string `bson:"zip_code"` } type Order struct { ID primitive.ObjectID `bson:"_id,omitempty"` UserID primitive.ObjectID `bson:"user_id"` Items []OrderItem `bson:"items"` TotalPrice float64 `bson:"total_price"` Status string `bson:"status"` Shipping Address `bson:"shipping_address"` CreatedAt time.Time `bson:"created_at"` } type OrderItem struct { ProductID primitive.ObjectID `bson:"product_id"` Quantity int `bson:"quantity"` Price float64 `bson:"price"` }三、CRUD操作
3.1 插入文档
func InsertUser(client *mongo.Client, user *User) (*mongo.InsertOneResult, error) { collection := client.Database("mydb").Collection("users") user.CreatedAt = time.Now() user.UpdatedAt = time.Now() result, err := collection.InsertOne(context.TODO(), user) if err != nil { return nil, err } return result, nil } func InsertUsers(client *mongo.Client, users []*User) (*mongo.InsertManyResult, error) { collection := client.Database("mydb").Collection("users") docs := make([]interface{}, len(users)) for i, user := range users { user.CreatedAt = time.Now() user.UpdatedAt = time.Now() docs[i] = user } result, err := collection.InsertMany(context.TODO(), docs) if err != nil { return nil, err } return result, nil }3.2 查询文档
func FindUserByID(client *mongo.Client, id primitive.ObjectID) (*User, error) { collection := client.Database("mydb").Collection("users") var user User err := collection.FindOne(context.TODO(), bson.M{"_id": id}).Decode(&user) if err != nil { return nil, err } return &user, nil } func FindUsersByAge(client *mongo.Client, minAge, maxAge int) ([]*User, error) { collection := client.Database("mydb").Collection("users") filter := bson.M{ "age": bson.M{ "$gte": minAge, "$lte": maxAge, }, } cursor, err := collection.Find(context.TODO(), filter) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var users []*User if err := cursor.All(context.TODO(), &users); err != nil { return nil, err } return users, nil } func FindUsersWithPagination(client *mongo.Client, page, pageSize int) ([]*User, error) { collection := client.Database("mydb").Collection("users") skip := (page - 1) * pageSize findOptions := options.Find(). SetSkip(int64(skip)). SetLimit(int64(pageSize)). SetSort(bson.D{{"created_at", -1}}) cursor, err := collection.Find(context.TODO(), bson.M{}, findOptions) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var users []*User if err := cursor.All(context.TODO(), &users); err != nil { return nil, err } return users, nil }3.3 更新文档
func UpdateUser(client *mongo.Client, id primitive.ObjectID, updates bson.M) (*mongo.UpdateResult, error) { collection := client.Database("mydb").Collection("users") filter := bson.M{"_id": id} update := bson.M{ "$set": updates, "$currentDate": bson.M{"updated_at": true}, } result, err := collection.UpdateOne(context.TODO(), filter, update) if err != nil { return nil, err } return result, nil } func UpdateUserEmail(client *mongo.Client, id primitive.ObjectID, newEmail string) error { _, err := UpdateUser(client, id, bson.M{"email": newEmail}) return err } func UpdateMultipleUsers(client *mongo.Client, filter bson.M, update bson.M) (*mongo.UpdateResult, error) { collection := client.Database("mydb").Collection("users") updateWithTimestamp := bson.M{ "$set": update, "$currentDate": bson.M{"updated_at": true}, } result, err := collection.UpdateMany(context.TODO(), filter, updateWithTimestamp) if err != nil { return nil, err } return result, nil }3.4 删除文档
func DeleteUser(client *mongo.Client, id primitive.ObjectID) (*mongo.DeleteResult, error) { collection := client.Database("mydb").Collection("users") result, err := collection.DeleteOne(context.TODO(), bson.M{"_id": id}) if err != nil { return nil, err } return result, nil } func DeleteUsersByFilter(client *mongo.Client, filter bson.M) (*mongo.DeleteResult, error) { collection := client.Database("mydb").Collection("users") result, err := collection.DeleteMany(context.TODO(), filter) if err != nil { return nil, err } return result, nil }四、高级查询
4.1 聚合管道
func GetProductStats(client *mongo.Client) ([]bson.M, error) { collection := client.Database("mydb").Collection("products") pipeline := mongo.Pipeline{ {{"$match", bson.M{"category": "electronics"}}}, {{"$group", bson.M{ "_id": "$category", "avgPrice": bson.M{"$avg": "$price"}, "count": bson.M{"$sum": 1}, "minPrice": bson.M{"$min": "$price"}, "maxPrice": bson.M{"$max": "$price"}, }}}, } cursor, err := collection.Aggregate(context.TODO(), pipeline) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var results []bson.M if err := cursor.All(context.TODO(), &results); err != nil { return nil, err } return results, nil } func GetUserOrders(client *mongo.Client, userID primitive.ObjectID) ([]bson.M, error) { collection := client.Database("mydb").Collection("orders") pipeline := mongo.Pipeline{ {{"$match", bson.M{"user_id": userID}}}, {{"$lookup", bson.M{ "from": "products", "localField": "items.product_id", "foreignField": "_id", "as": "products", }}}, {{"$sort", bson.M{"created_at": -1}}}, } cursor, err := collection.Aggregate(context.TODO(), pipeline) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var results []bson.M if err := cursor.All(context.TODO(), &results); err != nil { return nil, err } return results, nil }4.2 文本搜索
func SearchProducts(client *mongo.Client, query string) ([]*Product, error) { collection := client.Database("mydb").Collection("products") filter := bson.M{ "$text": bson.M{"$search": query}, } findOptions := options.Find(). SetSort(bson.M{"score": bson.M{"$meta": "textScore"}}) cursor, err := collection.Find(context.TODO(), filter, findOptions) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var products []*Product if err := cursor.All(context.TODO(), &products); err != nil { return nil, err } return products, nil }五、索引优化
5.1 创建索引
func CreateIndexes(client *mongo.Client) error { collection := client.Database("mydb").Collection("users") // 创建单字段索引 emailIndex := mongo.IndexModel{ Keys: bson.D{{"email", 1}}, Options: options.Index().SetUnique(true), } _, err := collection.Indexes().CreateOne(context.TODO(), emailIndex) if err != nil { return err } // 创建复合索引 ageCreatedIndex := mongo.IndexModel{ Keys: bson.D{{"age", 1}, {"created_at", -1}}, } _, err = collection.Indexes().CreateOne(context.TODO(), ageCreatedIndex) if err != nil { return err } // 创建文本索引 textIndex := mongo.IndexModel{ Keys: bson.D{{"name", "text"}, {"description", "text"}}, } _, err = collection.Indexes().CreateOne(context.TODO(), textIndex) if err != nil { return err } return nil } func GetIndexes(client *mongo.Client) ([]bson.M, error) { collection := client.Database("mydb").Collection("users") cursor, err := collection.Indexes().List(context.TODO()) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var indexes []bson.M if err := cursor.All(context.TODO(), &indexes); err != nil { return nil, err } return indexes, nil }六、事务处理
6.1 单文档事务
func TransferStock(client *mongo.Client, fromProductID, toProductID primitive.ObjectID, quantity int) error { ctx := context.TODO() session, err := client.StartSession() if err != nil { return err } defer session.EndSession(ctx) // 开始事务 err = session.StartTransaction() if err != nil { return err } collection := client.Database("mydb").Collection("products") // 扣除源商品库存 fromFilter := bson.M{"_id": fromProductID, "stock": bson.M{"$gte": quantity}} fromUpdate := bson.M{"$inc": bson.M{"stock": -quantity}} result, err := collection.UpdateOne(ctx, fromFilter, fromUpdate) if err != nil { session.AbortTransaction(ctx) return err } if result.ModifiedCount == 0 { session.AbortTransaction(ctx) return fmt.Errorf("insufficient stock") } // 增加目标商品库存 toFilter := bson.M{"_id": toProductID} toUpdate := bson.M{"$inc": bson.M{"stock": quantity}} _, err = collection.UpdateOne(ctx, toFilter, toUpdate) if err != nil { session.AbortTransaction(ctx) return err } // 提交事务 err = session.CommitTransaction(ctx) if err != nil { return err } return nil }七、连接池与性能优化
7.1 连接池配置
func NewMongoClient() (*mongo.Client, error) { clientOptions := options.Client(). ApplyURI("mongodb://localhost:27017"). SetConnectTimeout(10 * time.Second). SetMaxPoolSize(100). SetMinPoolSize(10). SetMaxConnIdleTime(30 * time.Second). SetServerSelectionTimeout(5 * time.Second) client, err := mongo.Connect(context.TODO(), clientOptions) if err != nil { return nil, err } return client, nil }7.2 查询性能优化
func OptimizedQuery(client *mongo.Client) ([]*User, error) { collection := client.Database("mydb").Collection("users") // 使用投影只返回需要的字段 projection := bson.M{ "name": 1, "email": 1, "_id": 0, } // 使用索引覆盖查询 filter := bson.M{ "age": bson.M{"$gt": 18}, } findOptions := options.Find(). SetProjection(projection). SetSort(bson.D{{"created_at", -1}}). SetLimit(100) cursor, err := collection.Find(context.TODO(), filter, findOptions) if err != nil { return nil, err } defer cursor.Close(context.TODO()) var users []*User if err := cursor.All(context.TODO(), &users); err != nil { return nil, err } return users, nil }结语
MongoDB的灵活文档模型与Go语言的强类型系统相结合,可以构建高效、可扩展的应用程序。通过合理使用索引、聚合管道和事务功能,可以充分发挥MongoDB的优势。希望本文的实践经验能帮助你更好地使用Go语言与MongoDB进行开发。
