You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Go MongoDB驱动更新数组元素时提示无匹配文档的问题

MongoDB UpdateOne返回"no document matched"的原因及修复

问题背景

我使用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设为false
  • target过滤器匹配目标贴纸包,将其isdefault设为true
  • 单次调用完成所有修改,避免两次调用的状态不一致问题

3. 验证参数一致性

确保传入的stickername和format与数据库中的值完全一致(包括大小写、空格、特殊字符等),避免因字符串不匹配导致filter无法找到目标文档。

内容的提问来源于stack exchange,提问作者Mysterio007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:27:00