【问题标题】:Unexpected behavior for transactions using mongo-go-driver with Azure Cosmos DB使用 mongo-go-driver 和 Azure Cosmos DB 的事务的意外行为
【发布时间】:2020-03-05 04:05:40
【问题描述】:

我确定我遗漏了一些东西,但我无法让以下简单事务按预期工作。这种行为与我能找到的所有其他 SO 问题不同。

以下函数MultipleInsertsTransaction() 的灵感来自official examples。
它成功写入一个文档,然后尝试写入第二个文档,这会返回错误,因为再次(故意)使用了相同的 ID。

我的理解是,这些文档都没有保存在数据库中,因为我们从未到达sc.CommitTransaction(sc),因此StartTransaction() 和AbortTransaction() 中的所有操作都应该回滚,或者甚至对其他会话不可见。

但是,事实并非如此。第一个文档写入,第二个按预期抛出错误,但是函数返回后,第一个文档被持久化到数据库中。

这笔交易有什么问题?我错过了什么?或者这甚至是预期的?

package main

import (
    "context"
    "fmt"
    "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/readconcern"
    "go.mongodb.org/mongo-driver/mongo/writeconcern"
)

const (
    db = "test"
    coll = "test"
)

func main() {
    client, _ := mongo.Connect(context.Background(), options.Client().ApplyURI("<put replica set connection string here>"))
    want, _ := client.Database(db).Collection(coll).CountDocuments(context.Background(), bson.M{})
    if err := MultipleInsertsTransaction(context.Background(), client); err != nil {
        fmt.Println("expected error occured...")
    }
    got, _ := client.Database(db).Collection(coll).CountDocuments(context.Background(), bson.M{})
    if got != want {
        fmt.Printf("expected %d entries in database, but got %d", want, got)
        return
    }
    fmt.Println("it worked!!")
}

func MultipleInsertsTransaction(ctx context.Context, client *mongo.Client) (err error) {
    return client.UseSession(ctx, func(sc mongo.SessionContext) error {
        err := sc.StartTransaction(options.Transaction().
            SetReadConcern(readconcern.Snapshot()).
            SetWriteConcern(writeconcern.New(writeconcern.WMajority())),
        )
        if err != nil {
            return err
        }

        id := primitive.NewObjectID()

        if _, err := client.Database(db).Collection(coll).InsertOne(sc, bson.M{"_id": id}); err != nil {
            sc.AbortTransaction(sc)
            return err
        }

        if _, err := client.Database(db).Collection(coll).InsertOne(sc, bson.M{"_id": id}); err != nil {
            sc.AbortTransaction(sc)
            return err
        }

        return sc.CommitTransaction(sc)
    })
}

非常感谢!


我也尝试了不同的实现,但(如预期的那样)也没有运气:

package main

import (
    "context"
    "fmt"
    "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"
)

const (
    db = "test"
    coll = "test"
)

func main() {
    client, _ := mongo.Connect(context.Background(), options.Client().ApplyURI("<put replica set connection string here>"))
    want, _ := client.Database(db).Collection(coll).CountDocuments(context.Background(), bson.M{})
    if err := MultipleInsertsTransaction(context.Background(), client); err != nil {
        fmt.Println("expected error occured...")
    }
    got, _ := client.Database(db).Collection(coll).CountDocuments(context.Background(), bson.M{})
    if got != want {
        fmt.Printf("expected %d entries in database, but got %d", want, got)
        return
    }
    fmt.Println("it worked!!")
}

func MultipleInsertsTransaction(ctx context.Context, client *mongo.Client) (err error) {
    var session mongo.Session
    if session, err = client.StartSession(); err != nil {
        return err
    }
    defer session.EndSession(context.Background())

    id := primitive.NewObjectID()

    if _, err := session.WithTransaction(ctx, func(sc mongo.SessionContext) (interface{}, error) {

        if _, err := client.Database(db).Collection(coll).InsertOne(sc, bson.M{"_id": id}); err != nil {
            sc.AbortTransaction(sc)
            return nil, err
        }

        if _, err := client.Database(db).Collection(coll).InsertOne(sc, bson.M{"_id": id}); err != nil {
            sc.AbortTransaction(sc)
            return nil, err
        }
        return nil, nil
    }); err != nil {
        return err
    }
    return
}

【问题讨论】:

  • 我已经使用 MongoDB 服务器 v4.2.2 和 Go 驱动程序 v1.3.1 测试了您的第一个代码 sn-p(复制粘贴),但未能复制您遇到的相同问题。我先创建了集合,然后运行了代码,它引发了重复键错误,并且没有任何内容插入到集合中。您能否确保集合中没有任何内容以确保第一个文档不是集合中的现有文档?
  • 感谢@Wan Bachtiar 试用,非常感谢!我的代码实际上适用于数据库中已经存在文档的情况,因为在测试之前就读取了want。我正在测试一个 azure cosmos 副本集。也许这就是问题所在!我也要试试生的蒙戈!再次感谢!
  • 完全正确。上面的代码使用 docker-compose 对本地副本集按预期工作。所以它一定是宇宙的东西。我更新了标题。它是 cosmos 上的错误(特别是因为它的行为与原始 mongo 副本集不同)还是错误?
  • 感谢您更新问题标题。我提供了一个答案,希望能更好地理解正在发生的事情。

标签: mongodb azure go transactions azure-cosmosdb


【解决方案1】:

Azure CosmosDB 的 MongoDB API 仅与 MongoDB Wire Protocol 3.6 版兼容。它是模拟与数据库的通信,核心数据库本身不是 MongoDB。

MongoDB multi-document transactions 在版本 4.0(当前为 v4.2)中引入。如果您使用的是支持事务和发送事务操作的 MongoDB 驱动程序,那么目前 CosmosDB 将不兼容它。根据您的用例,您可能会发现 MongoDB Atlas 有用。

【讨论】:

    猜你喜欢
    • 2018-02-21
    • 2023-02-21
    • 2019-03-30
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 2021-11-14
    • 2020-08-03
    相关资源
    最近更新 更多