使用Go MongoDB驱动更新数组元素时提示无匹配文档的问题
问题背景
我使用Go语言的mongo-driver包开发功能,目标是:针对指定用户ID,将其指定贴纸类型的所有贴纸包isdefault设为false,同时将传入的特定贴纸包isdefault设为true,确保同类型下仅保留一个默认贴纸包。但执行函数后返回"no document matched",代码与数据库结构如下:
代码实现
package src import ( "context" "fmt" "log" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) var ( client *mongo.Client stickerscol *mongo.Collection ) func init() { log.Println("Setting up database...") var err error client, err = mongo.Connect(context.Background(), options.Client().ApplyURI(Envars.DbUrl)) if err != nil { log.Fatal(err) } log.Println("Connected to database") stickerscol = client.Database("stickersbot").Collection("stickers") chatcol = client.Database("stickersbot").Collection("chat") } func ChangeDefaultPack(userid int64, stickername string, format string) error { fmt.Println(userid, stickername, format) filter := bson.M{ "_id": userid, "stickers.stickername": stickername, "stickers.stickerpacktype": format, } updateOther := bson.M{ "$set": bson.M{ "stickers.$[elem].isdefault": false, }, } update := bson.M{ "$set": bson.M{ "stickers.$.isdefault": true, }, } arrayFilters := options.ArrayFilters{ Filters: []interface{}{bson.M{"elem.stickerpacktype": format}}, } updateOptions := options.Update().SetArrayFilters(arrayFilters) _, err := stickerscol.UpdateMany(context.TODO(), filter, updateOther, updateOptions) if err != nil { return err } ok, err := stickerscol.UpdateOne(context.Background(), filter, update, options.Update().SetUpsert(true)) if err != nil { return err } if ok.ModifiedCount == 0 { fmt.Println("no document matched") return fmt.Errorf("no document matched") } return err }
数据库结构示例
{ "_id": 1633375527, "stickers": [ { "stickerpacktype": "static", "stickername": "gVtOy_1633375527_by_ChadManBot", "isdefault": true }, { "stickerpacktype": "animated", "stickername": "zIjsa_1633375527_by_ChadManBot", "isdefault": true }, { "stickerpacktype": "video", "stickername": "bJBle_1633375527_by_ChadManBot", "isdefault": true }, { "stickerpacktype": "static", "stickername": "iujiw_1633375527_by_ChadManBot", "isdefault": false } ], "stickerpackcount": 2 }
问题原因
核心问题出在filter的数组匹配逻辑错误:
你当前的filter写法:
filter := bson.M{ "_id": userid, "stickers.stickername": stickername, "stickers.stickerpacktype": format, }
这种写法会匹配满足以下条件的文档:
_id等于指定用户ID- 数组中存在至少一个元素的
stickername匹配传入值 - 数组中存在至少一个元素的
stickerpacktype匹配传入值
但这两个元素可以是数组中的不同元素,并非要求同一个元素同时满足两个条件。如果文档中没有元素同时符合stickername和format,filter依然会匹配文档,但后续的UpdateOne无法找到可修改的目标元素,导致ModifiedCount为0,触发"no document matched"错误。
此外,两次数据库调用的逻辑也存在隐患:UpdateMany会将同类型所有贴纸包设为false,包括目标贴纸包,但如果filter本身匹配逻辑错误,UpdateMany可能修改了错误的元素,后续UpdateOne也无法定位到正确目标。
修复方案
1. 修正filter,使用$elemMatch确保元素同时满足条件
将filter修改为使用$elemMatch,确保数组中存在同一个元素同时匹配stickername和format:
filter := bson.M{ "_id": userid, "stickers": bson.M{ "$elemMatch": bson.M{ "stickername": stickername, "stickerpacktype": format, }, }, }
这样filter只会匹配存在目标贴纸包的文档,确保后续update操作能定位到正确元素。
2. 优化逻辑,单次数据库调用完成所有修改
可以通过一次UpdateOne操作,结合多个arrayFilters同时处理目标元素和同类型其他元素,减少数据库交互:
func ChangeDefaultPack(userid int64, stickername string, format string) error { fmt.Println(userid, stickername, format) filter := bson.M{ "_id": userid, "stickers": bson.M{ "$elemMatch": bson.M{ "stickername": stickername, "stickerpacktype": format, }, }, } update := bson.M{ "$set": bson.M{ "stickers.$[other].isdefault": false, "stickers.$[target].isdefault": true, }, } arrayFilters := options.ArrayFilters{ Filters: []interface{}{ bson.M{"other.stickerpacktype": format, "other.stickername": bson.M{"$ne": stickername}}, bson.M{"target.stickername": stickername, "target.stickerpacktype": format}, }, } updateOptions := options.Update().SetArrayFilters(arrayFilters) result, err := stickerscol.UpdateOne(context.Background(), filter, update, updateOptions) if err != nil { return err } if result.ModifiedCount == 0 && result.UpsertedCount == 0 { fmt.Println("no document matched") return fmt.Errorf("no document matched") } return nil }
这个逻辑中:
other过滤器匹配同类型但非目标的贴纸包,将其isdefault设为falsetarget过滤器匹配目标贴纸包,将其isdefault设为true- 单次调用完成所有修改,避免两次调用的状态不一致问题
3. 验证参数一致性
确保传入的stickername和format与数据库中的值完全一致(包括大小写、空格、特殊字符等),避免因字符串不匹配导致filter无法找到目标文档。
内容的提问来源于stack exchange,提问作者Mysterio007

